diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..314a38b25 --- /dev/null +++ b/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + "@babel/preset-stage-3" + ], + "env": { + "COVERAGE": { + "plugins": [ + "istanbul" + ] + } + } +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index fa17ff1f3..a7fe2772e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,13 +1,13 @@ -FROM ubuntu:disco +FROM quay.io/wekan/ubuntu:groovy-20210115 LABEL maintainer="sgr" -ENV BUILD_DEPS="gnupg gosu bsdtar wget curl bzip2 g++ build-essential python git ca-certificates iproute2" +ENV BUILD_DEPS="gnupg gosu libarchive-tools wget curl bzip2 g++ build-essential python git ca-certificates iproute2" ENV DEBIAN_FRONTEND=noninteractive ENV \ DEBUG=false \ - NODE_VERSION=8.17.0 \ - METEOR_RELEASE=1.8.1 \ + NODE_VERSION=v12.22.3 \ + METEOR_RELEASE=1.10.2 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=latest \ @@ -15,16 +15,20 @@ ENV \ ARCHITECTURE=linux-x64 \ SRC_PATH=./ \ WITH_API=true \ + RESULTS_PER_PAGE="" \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \ - RICHER_CARD_COMMENT_EDITOR=true \ + RICHER_CARD_COMMENT_EDITOR=false \ + CARD_OPENED_WEBHOOK_ENABLED=false \ + ATTACHMENTS_STORE_PATH="" \ MAX_IMAGE_PIXEL="" \ IMAGE_COMPRESS_RATIO="" \ - BIGEVENTS_PATTERN="" \ + NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \ + BIGEVENTS_PATTERN=NONE \ NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" \ NOTIFY_DUE_AT_HOUR_OF_DAY="" \ EMAIL_NOTIFICATION_TIMEOUT=30000 \ @@ -36,6 +40,8 @@ ENV \ TRUSTED_URL="" \ WEBHOOKS_ATTRIBUTES="" \ OAUTH2_ENABLED=false \ + OAUTH2_CA_CERT="" \ + OAUTH2_ADFS_ENABLED=false \ OAUTH2_LOGIN_STYLE=redirect \ OAUTH2_CLIENT_ID="" \ OAUTH2_SECRET="" \ @@ -108,23 +114,40 @@ ENV \ CORS="" \ CORS_ALLOW_HEADERS="" \ CORS_EXPOSE_HEADERS="" \ - DEFAULT_AUTHENTICATION_METHOD="" + DEFAULT_AUTHENTICATION_METHOD="" \ + PASSWORD_LOGIN_ENABLED=true \ + CAS_ENABLED=false \ + CAS_BASE_URL="" \ + CAS_LOGIN_URL="" \ + CAS_VALIDATE_URL="" \ + SAML_ENABLED=false \ + SAML_PROVIDER="" \ + SAML_ENTRYPOINT="" \ + SAML_ISSUER="" \ + SAML_CERT="" \ + SAML_IDPSLO_REDIRECTURL="" \ + SAML_PRIVATE_KEYFILE="" \ + SAML_PUBLIC_CERTFILE="" \ + SAML_IDENTIFIER_FORMAT="" \ + SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \ + SAML_ATTRIBUTES="" \ + DEFAULT_WAIT_SPINNER="" # Install OS RUN set -o xtrace \ && useradd --user-group -m --system --home-dir /home/wekan wekan \ && apt-get update \ - && apt-get install --assume-yes --no-install-recommends apt-utils apt-transport-https ca-certificates 2>&1 \ - && apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS} + && apt-get install --assume-yes --no-install-recommends apt-utils apt-transport-https ca-certificates 2>&1 \ + && apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS} # Install NodeJS RUN set -o xtrace \ && cd /tmp \ - && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-$ARCHITECTURE.tar.xz" \ - && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ - && grep " node-v$NODE_VERSION-$ARCHITECTURE.tar.xz\$" SHASUMS256.txt.asc | sha256sum -c - \ - && tar -xJf "node-v$NODE_VERSION-$ARCHITECTURE.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ - && rm "node-v$NODE_VERSION-$ARCHITECTURE.tar.xz" SHASUMS256.txt.asc \ + && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$ARCHITECTURE.tar.xz" \ + && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt.asc" \ + && grep " node-$NODE_VERSION-$ARCHITECTURE.tar.xz\$" SHASUMS256.txt.asc | sha256sum -c - \ + && tar -xJf "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ + && rm "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" SHASUMS256.txt.asc \ && ln -s /usr/local/bin/node /usr/local/bin/nodejs \ && mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config \ && npm install -g npm@${NPM_VERSION} \ @@ -146,17 +169,65 @@ RUN set -o xtrace \ ENV PATH=$PATH:/home/wekan/.meteor/ -# Copy source dir USER root RUN echo "export PATH=$PATH" >> /etc/environment -RUN set -o xtrace \ - && mkdir /home/wekan/app +USER wekan -COPY ${SRC_PATH} /home/wekan/app/ +# Copy source dir +RUN set -o xtrace \ + && mkdir -p /home/wekan/app/.meteor \ + && mkdir -p /home/wekan/app/packages + +COPY \ + .meteor/.finished-upgraders \ + .meteor/.id \ + .meteor/cordova-plugins \ + .meteor/packages \ + .meteor/platforms \ + .meteor/release \ + .meteor/versions \ + /home/wekan/app/.meteor/ + +COPY \ + package.json \ + settings.json \ + /home/wekan/app/ + +COPY \ + packages \ + /home/wekan/app/packages/ + +USER root RUN set -o xtrace \ && chown -R wekan:wekan /home/wekan/app /home/wekan/.meteor USER wekan + +RUN \ + set -o xtrace && \ + sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \ + cd /home/wekan/.meteor && \ + /home/wekan/.meteor/meteor -- help; + +RUN \ + set -o xtrace && \ + # Build app + cd /home/wekan/app && \ + /home/wekan/.meteor/meteor add standard-minifier-js && \ + /home/wekan/.meteor/meteor npm install && \ + /home/wekan/.meteor/meteor build --directory /home/wekan/app_build + +RUN \ + set -o xtrace && \ + cd /home/wekan/app_build/bundle/programs/server/ && \ + chmod u+w package.json npm-shrinkwrap.json && \ + npm install + +ENV PORT=3000 +EXPOSE $PORT +WORKDIR /home/wekan/app + +CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"] diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index fab770560..d1e91b858 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -3,17 +3,18 @@ version: '3.7' services: wekandb-dev: - image: mongo:4.0.12 + image: mongo:4.4 container_name: wekan-dev-db restart: unless-stopped - command: mongod --smallfiles --oplogSize 128 + command: mongod --oplogSize 128 networks: - wekan-dev-tier expose: - 27017 volumes: - - wekan-dev-db:/data/db - - wekan-dev-db-dump:/dump + - ./volumes/wekan-db:/data/db + - ./volumes/wekan-db-dump:/dump + - /etc/localtime:/etc/localtime:ro wekan-dev: container_name: wekan-dev-app @@ -35,9 +36,13 @@ services: depends_on: - wekandb-dev volumes: - - ..:/app:delegated - command: - sleep infinity + - ../client:/home/wekan/app/client + - ../models:/home/wekan/app/models + - ../config:/home/wekan/app/config + - ../i18n:/home/wekan/app/i18n + - ../server:/home/wekan/app/server + - ../public:/home/wekan/app/public + - /etc/localtime:/etc/localtime:ro volumes: wekan-dev-db: diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..88a4b6897 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +*~ +*.swp +.meteor-spk +*.sublime-workspace +tmp/ +node_modules/ +npm-debug.log +.gitmodules +.vscode/ +.idea/ +.build/* +**/parts/ +**/stage +**/prime +**/*.snap +snap/.snapcraft/ +.idea +.DS_Store +.DS_Store? +.build* +*.browserify.js.cached +*.browserify.js.map +.build* +versions.json +.versions +.npm +.build* +._* +.Trashes +Thumbs.db +ehthumbs.db +.eslintcache +.meteor/local +.devcontainer/docker-compose.extend.yml +.devcontainer/volumes*/ +.git diff --git a/.eslintrc.json b/.eslintrc.json index 2de7450fd..97ed9297b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,6 +11,7 @@ "browser": true, "meteor": true }, + "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" @@ -44,7 +45,7 @@ "no-spaced-func": 2, "no-trailing-spaces": 2, "operator-linebreak": 2, - "quotes": [2, "single"], + "quotes": [2, "single", { "avoidEscape": true }], "semi-spacing": 2, "space-unary-ops": 2, "arrow-spacing": 2, diff --git a/.sandstorm-meteor-1.8/snapcraft.yaml b/.future-snap/broken-snapcraft.yaml similarity index 91% rename from .sandstorm-meteor-1.8/snapcraft.yaml rename to .future-snap/broken-snapcraft.yaml index 2f965fe16..55f453a93 100644 --- a/.sandstorm-meteor-1.8/snapcraft.yaml +++ b/.future-snap/broken-snapcraft.yaml @@ -65,9 +65,9 @@ apps: parts: mongodb: - source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz + source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz plugin: dump - stage-packages: [libssl1.0.0] + stage-packages: [libssl1.0.0, libcurl3] filesets: mongo: - usr @@ -81,19 +81,20 @@ parts: wekan: source: . plugin: nodejs - node-engine: 8.17.0 + node-engine: 12.22.3 node-packages: - node-gyp - node-pre-gyp - - fibers@2.0.0 + - fibers build-packages: - ca-certificates - apt-utils - python -# - python3 + - python3 - g++ - capnproto - curl + - libcurl3 - execstack - nodejs - npm @@ -104,6 +105,18 @@ parts: rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules # Create the OpenAPI specification rm -rf .build + ## Use Meteor 1.8.x on Snap + #rm -rf .meteor + #mv .snap-meteor-1.8/.meteor . + #mv .snap-meteor-1.8/package.json . + #mv .snap-meteor-1.8/package-lock.json . + ## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those + #mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/ + #mv .snap-meteor-1.8/export.js models/ + #mv .snap-meteor-1.8/wekanCreator.js models/ + #mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js + #mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js + rm -rf .snap-meteor-1.8 #mkdir -p .build/python #cd .build/python #git clone --depth 1 -b master https://github.com/Kronuz/esprima-python diff --git a/.future-snap/old-rebuild-wekan.sh b/.future-snap/old-rebuild-wekan.sh new file mode 100755 index 000000000..62dbf409c --- /dev/null +++ b/.future-snap/old-rebuild-wekan.sh @@ -0,0 +1,198 @@ +#!/bin/bash + +echo "Note: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8" +echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly." +echo " You can still use any other locale as your main locale." + +#Below script installs newest node 8.x for Debian/Ubuntu/Mint. +#NODE_VERSION=12.21.0 +#X64NODE="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" + +function pause(){ + read -p "$*" +} + +function cprec(){ + if [[ -d "$1" ]]; then + if [[ ! -d "$2" ]]; then + sudo mkdir -p "$2" + fi + + for i in $(ls -A "$1"); do + cprec "$1/$i" "$2/$i" + done + else + sudo cp "$1" "$2" + fi +} + +# sudo npm doesn't work right, so this is a workaround +function npm_call(){ + TMPDIR="/tmp/tmp_npm_prefix" + if [[ -d "$TMPDIR" ]]; then + rm -rf $TMPDIR + fi + mkdir $TMPDIR + NPM_PREFIX="$(npm config get prefix)" + npm config set prefix $TMPDIR + npm "$@" + npm config set prefix "$NPM_PREFIX" + + echo "Moving files to $NPM_PREFIX" + for i in $(ls -A $TMPDIR); do + cprec "$TMPDIR/$i" "$NPM_PREFIX/$i" + done + rm -rf $TMPDIR +} + +#function wekan_repo_check(){ +## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN +# git_remotes="$(git remote show 2>/dev/null)" +# res="" +# for i in $git_remotes; do +# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')" +# if [[ "$res" == "wekan/wekan" ]]; then +# break +# fi +# done +# +# if [[ "$res" != "wekan/wekan" ]]; then +# echo "$PWD is not a wekan repository" +# exit; +# fi +#} + +echo +PS3='Please enter your choice: ' +options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Quit") + +select opt in "${options[@]}" +do + case $opt in + "Install Wekan dependencies") + + if [[ "$OSTYPE" == "linux-gnu" ]]; then + echo "Linux"; + # Debian, Ubuntu, Mint + sudo apt-get install -y build-essential gcc g++ make git curl wget + # npm nodejs + #sudo npm -g install npm + curl -0 -L https://npmjs.org/install.sh | sudo sh + sudo chown -R $(id -u):$(id -g) $HOME/.npm + sudo npm -g install n + sudo n 12.21.0 + #curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - + #sudo apt-get install -y nodejs + elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "macOS"; + pause '1) Install XCode 2) Install Node 8.x from https://nodejs.org/en/ 3) Press [Enter] key to continue.' + elif [[ "$OSTYPE" == "cygwin" ]]; then + # POSIX compatibility layer and Linux environment emulation for Windows + echo "TODO: Add Cygwin"; + exit; + elif [[ "$OSTYPE" == "msys" ]]; then + # Lightweight shell and GNU utilities compiled for Windows (part of MinGW) + echo "TODO: Add msys on Windows"; + exit; + elif [[ "$OSTYPE" == "win32" ]]; then + # I'm not sure this can happen. + echo "TODO: Add Windows"; + exit; + elif [[ "$OSTYPE" == "freebsd"* ]]; then + echo "TODO: Add FreeBSD"; + exit; + else + echo "Unknown" + echo ${OSTYPE} + exit; + fi + + ## Latest npm with Meteor 1.8.x + npm_call -g install npm + npm_call -g install node-gyp + # Latest fibers for Meteor 1.8.x + sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp + npm_call -g install fibers + # Install Meteor, if it's not yet installed + curl https://install.meteor.com | bash + sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor + break + ;; + + "Build Wekan") + echo "Building Wekan." + #wekan_repo_check + # REPOS BELOW ARE INCLUDED TO WEKAN REPO + #rm -rf packages/kadira-flow-router packages/meteor-useraccounts-core packages/meteor-accounts-cas packages/wekan-ldap packages/wekan-ldap packages/wekan-scrfollbar packages/meteor-accounts-oidc packages/markdown + #mkdir packages + #cd packages + #git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router + #git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core + #git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git + #git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git + #git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git + #git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git + #git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git + #mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc + #mv meteor-accounts-oidc/packages/switch_oidc wekan_oidc + #rm -rf meteor-accounts-oidc + #if [[ "$OSTYPE" == "darwin"* ]]; then + # echo "sed at macOS"; + # sed -i '' 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js + #else + # echo "sed at ${OSTYPE}" + # sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js + #fi + #cd .. + sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor + rm -rf node_modules .meteor/local + npm install + rm -rf .build + meteor build .build --directory + cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js + # Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. + rm -rf .build/bundle/programs/web.browser.legacy + #Removed binary version of bcrypt because of security vulnerability that is not fixed yet. + #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac + #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c + #cd ~/repos/wekan/.build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt + #rm -rf node_modules/bcrypt + #meteor npm install bcrypt + cd .build/bundle/programs/server + rm -rf node_modules + npm install + #meteor npm install bcrypt + cd ../../../.. + echo Done. + break + ;; + + "Run Meteor for dev on http://localhost:4000") + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 + break + ;; + + "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000") + IPADDRESS=$(ip a | grep 'noprefixroute' | grep 'inet ' | cut -d: -f2 | awk '{ print $2}' | cut -d '/' -f 1) + echo "Your IP address is $IPADDRESS" + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 + break + ;; + + "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT") + ip address + echo "From above list, what is your IP address?" + read IPADDRESS + echo "On what port you would like to run Wekan?" + read PORT + echo "ROOT_URL=http://$IPADDRESS:$PORT" + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --exclude-archs web.browser.legacy,web.cordova --port $PORT + break + ;; + + "Quit") + break + ;; + *) echo invalid option;; + esac +done diff --git a/.sandstorm-meteor-1.8/future/snapcraft.yaml b/.future-snap/snapcraft.yaml similarity index 99% rename from .sandstorm-meteor-1.8/future/snapcraft.yaml rename to .future-snap/snapcraft.yaml index a84c5d1f4..75ae5a153 100644 --- a/.sandstorm-meteor-1.8/future/snapcraft.yaml +++ b/.future-snap/snapcraft.yaml @@ -83,7 +83,7 @@ parts: wekan: source: . plugin: nodejs - node-engine: 12.14.1 + node-engine: 12.22.3 node-packages: - node-gyp - node-pre-gyp diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5cfbd5386..0418c1bf4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,8 +1,16 @@ ## Issue +Note: With Docker, please don't use latest tag. Only use release tags. +See https://github.com/wekan/wekan/issues/3874 + +If you can not login for any reason: +- https://github.com/wekan/wekan/wiki/Forgot-Password + +Email settings: +- https://github.com/wekan/wekan/wiki/Troubleshooting-Mail + Add these issues to elsewhere: -- Snap: https://github.com/wekan/wekan-snap/issues -- LDAP: https://github.com/wekan/wekan-ldap/issues +- SECURITY ISSUES: https://github.com/wekan/wekan/blob/master/SECURITY.md - UCS: https://github.com/wekan/univention/issues Other Wekan issues can be added here. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..dac7475cd --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,62 @@ +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 16 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['javascript', 'python'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml new file mode 100644 index 000000000..3d678a510 --- /dev/null +++ b/.github/workflows/test_suite.yml @@ -0,0 +1,160 @@ +name: Test suite + +on: + push: + branches: + - master + pull_request: + +jobs: +# the following are optional jobs and need to be configured according +# to this project's settings: +# +# lintcode: +# name: Javascript lint +# runs-on: ubuntu-latest +# steps: +# - name: checkout +# uses: actions/checkout@v2 +# +# - name: setup node +# uses: actions/setup-node@v1 +# with: +# node-version: '12.x' +# +# - name: cache dependencies +# uses: actions/cache@v1 +# with: +# path: ~/.npm +# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} +# restore-keys: | +# ${{ runner.os }}-node- +# +# - run: npm install +# - run: npm run lint:code +# +# lintstyle: +# name: SCSS lint +# runs-on: ubuntu-latest +# needs: [lintcode] +# steps: +# - name: checkout +# uses: actions/checkout@v2 +# +# - name: setup node +# uses: actions/setup-node@v1 +# with: +# node-version: '12.x' +# +# - name: cache dependencies +# uses: actions/cache@v1 +# with: +# path: ~/.npm +# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} +# restore-keys: | +# ${{ runner.os }}-node- +# - run: npm install +# - run: npm run lint:style +# +# lintdocs: +# name: documentation lint +# runs-on: ubuntu-latest +# needs: [lintcode,lintstyle] +# steps: +# - name: checkout +# uses: actions/checkout@v2 +# +# - name: setup node +# uses: actions/setup-node@v1 +# with: +# node-version: '12.x' +# +# - name: cache dependencies +# uses: actions/cache@v1 +# with: +# path: ~/.npm +# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} +# restore-keys: | +# ${{ runner.os }}-node- +# +# - run: npm install +# - run: npm run lint:markdown + + tests: + name: Meteor ${{ matrix.meteor }} tests + runs-on: ubuntu-latest + steps: + + # CHECKOUTS + - name: Checkout + uses: actions/checkout@v2 + + # CACHING + - name: Install Meteor + id: cache-meteor-install + uses: actions/cache@v2 + with: + path: ~/.meteor + key: v1-meteor-${{ hashFiles('.meteor/versions') }} + restore-keys: | + v1-meteor- + + - name: Cache NPM dependencies + id: cache-meteor-npm + uses: actions/cache@v2 + with: + path: ~/.npm + key: v1-npm-${{ hashFiles('package-lock.json') }} + restore-keys: | + v1-npm- + + - name: Cache Meteor build + id: cache-meteor-build + uses: actions/cache@v2 + with: + path: | + .meteor/local/resolver-result-cache.json + .meteor/local/plugin-cache + .meteor/local/isopacks + .meteor/local/bundler-cache/scanner + key: v1-meteor_build_cache-${{ github.ref }}-${{ github.sha }} + restore-key: | + v1-meteor_build_cache- + + - name: Setup meteor + uses: meteorengineer/setup-meteor@v1 + with: + meteor-release: '2.2' + + - name: Install NPM Dependencies + run: meteor npm ci + + - name: Run Tests + run: sh ./test-wekan.sh -cv + + - name: Upload coverage + uses: actions/upload-artifact@v2 + with: + name: coverage-folder + path: .coverage/ + + coverage: + name: Coverage report + runs-on: ubuntu-latest + needs: [tests] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Download coverage + uses: actions/download-artifact@v2 + with: + name: coverage-folder + path: .coverage/ + + + - name: Coverage Report + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + with: + path: ".coverage/lcov.info" + min_coverage: 1 # TODO add tests and increase to 95! diff --git a/.gitignore b/.gitignore index 519d5d979..026e1b515 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ tmp/ node_modules/ npm-debug.log +.gitmodules .vscode/ .idea/ .build/* @@ -30,5 +31,6 @@ Thumbs.db ehthumbs.db .eslintcache .meteor/local -.meteor-1.6-snap/.meteor/local .devcontainer/docker-compose.extend.yml +.devcontainer/volumes*/ +.coverage diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 000000000..be6e71f5d --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,10 @@ +FROM gitpod/workspace-mongodb + +USER gitpod + +# Install custom tools, runtime, etc. using apt-get +# For example, the command below would install "bastet" - a command line tetris clone: +# +# RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/* +# +# More information: https://www.gitpod.io/docs/config-docker/ diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..6463af3bb --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,4 @@ +tasks: + - init: npm install +image: + file: .gitpod.Dockerfile diff --git a/.meteor/packages b/.meteor/packages index c8d129d40..40e3c7213 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -6,8 +6,8 @@ meteor-base@1.4.0 # Build system -ecmascript@0.14.2 -standard-minifier-css@1.6.0 +ecmascript@0.15.1 +standard-minifier-css@1.7.2 standard-minifier-js@2.6.0 mquandalle:jade coffeescript@2.4.1! @@ -17,13 +17,13 @@ es5-shim@4.8.0 # Collections aldeed:collection2 -cfs:standard-packages +wekan-cfs-standard-packages cottz:publish-relations dburles:collection-helpers idmontie:migrations matb33:collection-hooks matteodem:easy-search -mongo@1.9.0 +mongo@1.11.0 mquandalle:collection-mutations # Account system @@ -70,24 +70,19 @@ rajit:bootstrap3-datepicker shell-server@0.5.0 simple:rest-accounts-password useraccounts:core -email@1.2.3 +email@2.0.0 horka:swipebox -dynamic-import@0.5.1 -staringatlights:fast-render +dynamic-import@0.6.0 -accounts-password@1.6.0 -cfs:gridfs +accounts-password@1.7.0 +wekan-cfs-gridfs rzymek:fullcalendar momentjs:moment@2.22.2 browser-policy-framing@1.1.0 mquandalle:moment msavin:usercache -wekan-scrollbar -mquandalle:perfect-scrollbar -mdg:meteor-apm-agent@3.2.0-rc.0! # Keep stylus in 1.1.0, because building v2 takes extra 52 minutes. coagmano:stylus@1.1.0! -lucasantoniassi:accounts-lockout meteorhacks:subs-manager meteorhacks:picker lamhieu:unblock @@ -95,6 +90,60 @@ meteorhacks:aggregate@1.3.0 wekan-markdown konecty:mongo-counter percolate:synced-cron +wekan-cfs-filesystem +steffo:meteor-accounts-saml +rajit:bootstrap3-datepicker-fi +rajit:bootstrap3-datepicker-ar +rajit:bootstrap3-datepicker-bg +rajit:bootstrap3-datepicker-br +rajit:bootstrap3-datepicker-ca +rajit:bootstrap3-datepicker-cs +rajit:bootstrap3-datepicker-da +rajit:bootstrap3-datepicker-de +rajit:bootstrap3-datepicker-el +rajit:bootstrap3-datepicker-en-gb +rajit:bootstrap3-datepicker-eo +rajit:bootstrap3-datepicker-es +rajit:bootstrap3-datepicker-eu +rajit:bootstrap3-datepicker-fa +rajit:bootstrap3-datepicker-fr +rajit:bootstrap3-datepicker-gl +rajit:bootstrap3-datepicker-he +rajit:bootstrap3-datepicker-hi +rajit:bootstrap3-datepicker-hu +rajit:bootstrap3-datepicker-hy +rajit:bootstrap3-datepicker-id +rajit:bootstrap3-datepicker-it +rajit:bootstrap3-datepicker-ja +rajit:bootstrap3-datepicker-ka +rajit:bootstrap3-datepicker-km +rajit:bootstrap3-datepicker-ko +rajit:bootstrap3-datepicker-lv +rajit:bootstrap3-datepicker-mk +rajit:bootstrap3-datepicker-mn +rajit:bootstrap3-datepicker-nb +rajit:bootstrap3-datepicker-nl +rajit:bootstrap3-datepicker-oc +rajit:bootstrap3-datepicker-pl +rajit:bootstrap3-datepicker-pt-br +rajit:bootstrap3-datepicker-pt +rajit:bootstrap3-datepicker-ro +rajit:bootstrap3-datepicker-ru +rajit:bootstrap3-datepicker-sl +rajit:bootstrap3-datepicker-sr +rajit:bootstrap3-datepicker-sv +rajit:bootstrap3-datepicker-sw +rajit:bootstrap3-datepicker-ta +rajit:bootstrap3-datepicker-th +rajit:bootstrap3-datepicker-tr +rajit:bootstrap3-datepicker-uk +rajit:bootstrap3-datepicker-vi +rajit:bootstrap3-datepicker-zh-cn +rajit:bootstrap3-datepicker-zh-tw +staringatlights:fast-render +spacebars easylogic:summernote -cfs:filesystem -ostrio:cookies +pascoual:pdfkit +wekan-accounts-lockout +lmieulet:meteor-coverage +meteortesting:mocha diff --git a/.meteor/release b/.meteor/release index d25978111..d8fd7cf54 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.10.1 +METEOR@2.2 diff --git a/.meteor/versions b/.meteor/versions index 062697eab..d78e3a94f 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,7 +1,7 @@ 3stack:presence@1.1.2 -accounts-base@1.6.0 +accounts-base@1.9.0 accounts-oauth@1.2.0 -accounts-password@1.6.0 +accounts-password@1.7.1 aldeed:collection2@2.10.0 aldeed:collection2-core@1.2.0 aldeed:schema-deny@1.1.0 @@ -10,37 +10,20 @@ aldeed:simple-schema@1.5.4 allow-deny@1.1.0 arillo:flow-router-helpers@0.5.2 audit-argument-checks@1.0.7 -autoupdate@1.6.0 -babel-compiler@7.5.3 +autoupdate@1.7.0 +babel-compiler@7.6.1 babel-runtime@1.5.0 base64@1.0.12 binary-heap@1.0.11 -blaze@2.3.4 -blaze-tools@1.0.10 -boilerplate-generator@1.7.0 +blaze@2.5.0 +blaze-tools@1.1.2 +boilerplate-generator@1.7.1 browser-policy-common@1.0.11 browser-policy-framing@1.1.0 caching-compiler@1.2.2 -caching-html-compiler@1.1.3 +caching-html-compiler@1.2.0 callback-hook@1.3.0 -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.1 chuangbo:cookie@1.1.0 coagmano:stylus@1.1.0 @@ -49,20 +32,20 @@ coffeescript-compiler@2.4.1 cottz:publish-relations@2.0.8 dburles:collection-helpers@1.1.0 ddp@1.4.0 -ddp-client@2.3.3 +ddp-client@2.4.1 ddp-common@1.4.0 -ddp-rate-limiter@1.0.7 -ddp-server@2.3.1 +ddp-rate-limiter@1.0.9 +ddp-server@2.3.3 deps@1.0.12 diff-sequence@1.1.1 -dynamic-import@0.5.2 +dynamic-import@0.6.0 easylogic:summernote@0.8.8 -ecmascript@0.14.3 +ecmascript@0.15.1 ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.10.0 -ecmascript-runtime-server@0.9.0 +ecmascript-runtime-client@0.11.1 +ecmascript-runtime-server@0.10.1 ejson@1.1.1 -email@1.2.3 +email@2.0.0 es5-shim@4.8.0 fastclick@1.0.13 fetch@0.1.1 @@ -70,10 +53,10 @@ fortawesome:fontawesome@4.7.0 geojson-utils@1.0.10 horka:swipebox@1.0.2 hot-code-push@1.0.4 -html-tools@1.0.11 -htmljs@1.0.11 -http@1.4.2 -id-map@1.1.0 +html-tools@1.1.2 +htmljs@1.1.1 +http@1.4.4 +id-map@1.1.1 idmontie:migrations@1.0.3 inter-process-messaging@0.1.1 jquery@1.11.11 @@ -84,14 +67,13 @@ kenton:accounts-sandstorm@0.7.0 konecty:mongo-counter@0.0.5_3 lamhieu:meteorx@2.1.1 lamhieu:unblock@1.0.0 -launch-screen@1.2.0 +launch-screen@1.2.1 livedata@1.0.18 +lmieulet:meteor-coverage@3.2.0 localstorage@1.2.0 -logging@1.1.20 -lucasantoniassi:accounts-lockout@1.0.0 +logging@1.2.0 matb33:collection-hooks@0.9.1 matteodem:easy-search@1.6.4 -mdg:meteor-apm-agent@3.2.5 mdg:validation-error@0.5.1 meteor@1.9.3 meteor-base@1.4.0 @@ -101,19 +83,22 @@ meteorhacks:collection-utils@1.2.0 meteorhacks:picker@1.0.3 meteorhacks:subs-manager@1.6.4 meteorspark:util@0.2.0 -minifier-css@1.5.0 +meteortesting:browser-tests@1.3.4 +meteortesting:mocha@2.0.1 +meteortesting:mocha-core@8.0.1 +minifier-css@1.5.4 minifier-js@2.6.0 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.5.0 +minimongo@1.6.2 mobile-status-bar@1.1.0 modern-browsers@0.1.5 -modules@0.15.0 +modules@0.16.0 modules-runtime@0.12.0 -momentjs:moment@2.24.0 -mongo@1.9.1 -mongo-decimal@0.1.1 +momentjs:moment@2.29.1 +mongo@1.11.1 +mongo-decimal@0.1.2 mongo-dev-server@1.1.0 -mongo-id@1.0.7 +mongo-id@1.0.8 mongo-livedata@1.0.12 mousetrap:mousetrap@1.4.6_1 mquandalle:autofocus@1.0.0 @@ -124,16 +109,15 @@ mquandalle:jquery-textcomplete@0.8.0_1 mquandalle:jquery-ui-drag-drop-sort@0.2.0 mquandalle:moment@1.0.1 mquandalle:mousetrap-bindglobal@0.0.1 -mquandalle:perfect-scrollbar@0.6.5_2 msavin:usercache@1.8.0 -npm-bcrypt@0.9.3 -npm-mongo@3.7.0 -oauth@1.3.0 +npm-bcrypt@0.9.4 +npm-mongo@3.9.0 +oauth@1.3.2 oauth2@1.3.0 observe-sequence@1.0.16 ongoworks:speakingurl@1.1.0 ordered-dict@1.1.0 -ostrio:cookies@2.6.0 +pascoual:pdfkit@1.0.7 peerlibrary:assert@0.3.0 peerlibrary:base-component@0.16.0 peerlibrary:blaze-components@0.15.1 @@ -144,11 +128,60 @@ promise@0.11.2 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 rajit:bootstrap3-datepicker@1.7.1_1 +rajit:bootstrap3-datepicker-ar@1.7.1 +rajit:bootstrap3-datepicker-bg@1.7.1 +rajit:bootstrap3-datepicker-br@1.7.1 +rajit:bootstrap3-datepicker-ca@1.7.1 +rajit:bootstrap3-datepicker-cs@1.7.1 +rajit:bootstrap3-datepicker-da@1.7.1 +rajit:bootstrap3-datepicker-de@1.7.1 +rajit:bootstrap3-datepicker-el@1.7.1 +rajit:bootstrap3-datepicker-en-gb@1.7.1 +rajit:bootstrap3-datepicker-eo@1.7.1 +rajit:bootstrap3-datepicker-es@1.7.1 +rajit:bootstrap3-datepicker-eu@1.7.1 +rajit:bootstrap3-datepicker-fa@1.7.1 +rajit:bootstrap3-datepicker-fi@1.7.1 +rajit:bootstrap3-datepicker-fr@1.7.1 +rajit:bootstrap3-datepicker-gl@1.7.1 +rajit:bootstrap3-datepicker-he@1.7.1 +rajit:bootstrap3-datepicker-hi@1.7.1 +rajit:bootstrap3-datepicker-hu@1.7.1 +rajit:bootstrap3-datepicker-hy@1.7.1 +rajit:bootstrap3-datepicker-id@1.7.1 +rajit:bootstrap3-datepicker-it@1.7.1 +rajit:bootstrap3-datepicker-ja@1.7.1 +rajit:bootstrap3-datepicker-ka@1.7.1 +rajit:bootstrap3-datepicker-km@1.7.1 +rajit:bootstrap3-datepicker-ko@1.7.1 +rajit:bootstrap3-datepicker-lv@1.7.1 +rajit:bootstrap3-datepicker-mk@1.7.1 +rajit:bootstrap3-datepicker-mn@1.7.1 +rajit:bootstrap3-datepicker-nb@1.7.1 +rajit:bootstrap3-datepicker-nl@1.7.1 +rajit:bootstrap3-datepicker-oc@1.7.1 +rajit:bootstrap3-datepicker-pl@1.7.1 +rajit:bootstrap3-datepicker-pt@1.7.1 +rajit:bootstrap3-datepicker-pt-br@1.7.1 +rajit:bootstrap3-datepicker-ro@1.7.1 +rajit:bootstrap3-datepicker-ru@1.7.1 +rajit:bootstrap3-datepicker-sl@1.7.1 +rajit:bootstrap3-datepicker-sr@1.7.1 +rajit:bootstrap3-datepicker-sv@1.7.1 +rajit:bootstrap3-datepicker-sw@1.7.1 +rajit:bootstrap3-datepicker-ta@1.7.1 +rajit:bootstrap3-datepicker-th@1.7.1 +rajit:bootstrap3-datepicker-tr@1.7.1 +rajit:bootstrap3-datepicker-uk@1.7.1 +rajit:bootstrap3-datepicker-vi@1.7.1 +rajit:bootstrap3-datepicker-zh-cn@1.7.1 +rajit:bootstrap3-datepicker-zh-tw@1.7.1 random@1.2.0 rate-limit@1.0.9 +react-fast-refresh@0.1.1 reactive-dict@1.3.0 reactive-var@1.0.11 -reload@1.3.0 +reload@1.3.1 retry@1.1.0 routepolicy@1.1.0 rzymek:fullcalendar@3.8.0 @@ -162,37 +195,56 @@ simple:json-routes@2.1.0 simple:rest-accounts-password@1.1.2 simple:rest-bearer-token-parser@1.0.1 simple:rest-json-error-handler@1.0.1 -socket-stream-client@0.2.3 +socket-stream-client@0.3.3 softwarerero:accounts-t9n@1.3.11 -spacebars@1.0.15 -spacebars-compiler@1.1.3 -srp@1.0.12 -standard-minifier-css@1.6.0 +spacebars@1.2.0 +spacebars-compiler@1.2.1 +srp@1.1.0 +standard-minifier-css@1.7.2 standard-minifier-js@2.6.0 -staringatlights:fast-render@3.2.0 +staringatlights:fast-render@3.3.0 staringatlights:inject-data@2.3.0 +steffo:meteor-accounts-saml@0.0.18 tap:i18n@1.8.2 templates:tabs@2.3.0 -templating@1.3.2 -templating-compiler@1.3.3 -templating-runtime@1.3.2 -templating-tools@1.1.2 +templating@1.4.0 +templating-compiler@1.4.1 +templating-runtime@1.4.0 +templating-tools@1.2.0 tracker@1.2.0 twbs:bootstrap@3.3.6 ui@1.0.13 underscore@1.0.10 -url@1.2.0 +url@1.3.2 useraccounts:core@1.14.2 useraccounts:flow-routing@1.14.2 useraccounts:unstyled@1.14.2 verron:autosize@3.0.8 -webapp@1.9.1 -webapp-hashing@1.0.9 +webapp@1.10.1 +webapp-hashing@1.1.0 wekan-accounts-cas@0.1.0 +wekan-accounts-lockout@1.0.0 wekan-accounts-oidc@1.0.10 +wekan-cfs-access-point@0.1.50 +wekan-cfs-base-package@0.0.30 +wekan-cfs-collection@0.5.5 +wekan-cfs-collection-filters@0.2.4 +wekan-cfs-data-man@0.0.6 +wekan-cfs-file@0.1.17 +wekan-cfs-filesystem@0.1.2 +wekan-cfs-gridfs@0.0.34 +wekan-cfs-http-methods@0.0.32 +wekan-cfs-http-publish@0.0.13 +wekan-cfs-power-queue@0.9.11 +wekan-cfs-reactive-list@0.0.9 +wekan-cfs-reactive-property@0.0.4 +wekan-cfs-standard-packages@0.5.10 +wekan-cfs-storage-adapter@0.2.4 +wekan-cfs-tempstore@0.1.6 +wekan-cfs-upload-http@0.0.21 +wekan-cfs-worker@0.1.5 wekan-ldap@0.0.2 -wekan-markdown@1.0.7 +wekan-markdown@1.0.9 wekan-oidc@1.0.12 -wekan-scrollbar@3.1.3 yasaricli:slugify@0.0.7 zimme:active-route@2.3.2 diff --git a/.sandstorm-meteor-1.8/.meteor/.finished-upgraders b/.sandstorm-meteor-1.8/.meteor/.finished-upgraders deleted file mode 100644 index bc5b50f7c..000000000 --- a/.sandstorm-meteor-1.8/.meteor/.finished-upgraders +++ /dev/null @@ -1,20 +0,0 @@ -# This file contains information which helps Meteor properly upgrade your -# app when you run 'meteor update'. You should check it into version control -# with your project. - -notices-for-0.9.0 -notices-for-0.9.1 -0.9.4-platform-file -notices-for-facebook-graph-api-2 -1.2.0-standard-minifiers-package -1.2.0-meteor-platform-split -1.2.0-cordova-changes -1.2.0-breaking-changes -1.3.0-split-minifiers-package -1.3.5-remove-old-dev-bundle-link -1.4.0-remove-old-dev-bundle-link -1.4.1-add-shell-server-package -1.4.3-split-account-service-packages -1.5-add-dynamic-import-package -1.7-split-underscore-from-meteor-base -1.8.3-split-jquery-from-blaze diff --git a/.sandstorm-meteor-1.8/.meteor/.gitignore b/.sandstorm-meteor-1.8/.meteor/.gitignore deleted file mode 100644 index 501f92e4b..000000000 --- a/.sandstorm-meteor-1.8/.meteor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dev_bundle -local diff --git a/.sandstorm-meteor-1.8/.meteor/.id b/.sandstorm-meteor-1.8/.meteor/.id deleted file mode 100644 index 0556ccf7b..000000000 --- a/.sandstorm-meteor-1.8/.meteor/.id +++ /dev/null @@ -1,7 +0,0 @@ -# This file contains a token that is unique to your project. -# Check it into your repository along with the rest of this directory. -# It can be used for purposes such as: -# - ensuring you don't accidentally deploy one app on top of another -# - providing package authors with aggregated statistics - -dvyihgykyzec6y1dpg diff --git a/.sandstorm-meteor-1.8/.meteor/packages b/.sandstorm-meteor-1.8/.meteor/packages deleted file mode 100644 index 8af7f3145..000000000 --- a/.sandstorm-meteor-1.8/.meteor/packages +++ /dev/null @@ -1,100 +0,0 @@ -# Meteor packages used by this project, one per line. -# -# 'meteor add' and 'meteor remove' will edit this file for you, -# but you can also edit it by hand. - -meteor-base@1.4.0 - -# Build system -ecmascript@0.13.2 -standard-minifier-css@1.5.4 -standard-minifier-js@2.5.2 -mquandalle:jade - -# Polyfills -es5-shim@4.8.0 - -# Collections -aldeed:collection2 -cfs:standard-packages -cottz:publish-relations -dburles:collection-helpers -idmontie:migrations -matb33:collection-hooks -matteodem:easy-search -mongo@1.7.0 -mquandalle:collection-mutations - -# Account system -kenton:accounts-sandstorm -service-configuration@1.0.11 -useraccounts:unstyled -useraccounts:flow-routing -wekan-ldap -wekan-accounts-cas -wekan-accounts-oidc - -# Utilities -check@1.3.1 -jquery@1.11.10 -random@1.1.0 -reactive-dict@1.3.0 -session@1.2.0 -tracker@1.2.0 -underscore@1.0.10 -3stack:presence -alethes:pages -arillo:flow-router-helpers -audit-argument-checks@1.0.7 -kadira:blaze-layout -kadira:dochead -mquandalle:autofocus -ongoworks:speakingurl -raix:handlebar-helpers -tap:i18n -http@1.4.2 - -# UI components -blaze -reactive-var@1.0.11 -fortawesome:fontawesome -mousetrap:mousetrap -mquandalle:jquery-textcomplete -mquandalle:jquery-ui-drag-drop-sort -mquandalle:mousetrap-bindglobal -peerlibrary:blaze-components@=0.15.1 -templates:tabs -verron:autosize -simple:json-routes -rajit:bootstrap3-datepicker -shell-server@0.4.0 -simple:rest-accounts-password -useraccounts:core -email@1.2.3 -horka:swipebox -dynamic-import@0.5.1 -staringatlights:fast-render - -accounts-password@1.5.2 -cfs:gridfs -rzymek:fullcalendar -momentjs:moment@2.22.2 -browser-policy-framing@1.1.0 -mquandalle:moment -msavin:usercache -wekan-scrollbar -mquandalle:perfect-scrollbar -mdg:meteor-apm-agent@3.2.0-rc.0! -# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes. -coagmano:stylus@1.1.0! -lucasantoniassi:accounts-lockout -meteorhacks:subs-manager -meteorhacks:picker -lamhieu:unblock -meteorhacks:aggregate@1.3.0 -wekan-markdown -konecty:mongo-counter -percolate:synced-cron -easylogic:summernote -cfs:filesystem -ostrio:cookies diff --git a/.sandstorm-meteor-1.8/.meteor/platforms b/.sandstorm-meteor-1.8/.meteor/platforms deleted file mode 100644 index efeba1b50..000000000 --- a/.sandstorm-meteor-1.8/.meteor/platforms +++ /dev/null @@ -1,2 +0,0 @@ -server -browser diff --git a/.sandstorm-meteor-1.8/.meteor/release b/.sandstorm-meteor-1.8/.meteor/release deleted file mode 100644 index bfccdc2c9..000000000 --- a/.sandstorm-meteor-1.8/.meteor/release +++ /dev/null @@ -1 +0,0 @@ -METEOR@1.8.3 diff --git a/.sandstorm-meteor-1.8/.meteor/versions b/.sandstorm-meteor-1.8/.meteor/versions deleted file mode 100644 index 65869431f..000000000 --- a/.sandstorm-meteor-1.8/.meteor/versions +++ /dev/null @@ -1,198 +0,0 @@ -3stack:presence@1.1.2 -accounts-base@1.4.5 -accounts-oauth@1.1.16 -accounts-password@1.5.2 -aldeed:collection2@2.10.0 -aldeed:collection2-core@1.2.0 -aldeed:schema-deny@1.1.0 -aldeed:schema-index@1.1.1 -aldeed:simple-schema@1.5.4 -alethes:pages@1.8.6 -allow-deny@1.1.0 -arillo:flow-router-helpers@0.5.2 -audit-argument-checks@1.0.7 -autoupdate@1.6.0 -babel-compiler@7.4.2 -babel-runtime@1.4.0 -base64@1.0.12 -binary-heap@1.0.11 -blaze@2.3.4 -blaze-tools@1.0.10 -boilerplate-generator@1.6.0 -browser-policy-common@1.0.11 -browser-policy-framing@1.1.0 -caching-compiler@1.2.1 -caching-html-compiler@1.1.3 -callback-hook@1.2.0 -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.1 -chuangbo:cookie@1.1.0 -coagmano:stylus@1.1.0 -coffeescript@1.0.17 -cottz:publish-relations@2.0.8 -dburles:collection-helpers@1.1.0 -ddp@1.4.0 -ddp-client@2.3.3 -ddp-common@1.4.0 -ddp-rate-limiter@1.0.7 -ddp-server@2.3.0 -deps@1.0.12 -diff-sequence@1.1.1 -dynamic-import@0.5.1 -easylogic:summernote@0.8.8 -ecmascript@0.13.2 -ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.9.0 -ecmascript-runtime-server@0.8.0 -ejson@1.1.1 -email@1.2.3 -es5-shim@4.8.0 -fastclick@1.0.13 -fetch@0.1.1 -fortawesome:fontawesome@4.7.0 -geojson-utils@1.0.10 -horka:swipebox@1.0.2 -hot-code-push@1.0.4 -html-tools@1.0.11 -htmljs@1.0.11 -http@1.4.2 -id-map@1.1.0 -idmontie:migrations@1.0.3 -inter-process-messaging@0.1.0 -jquery@1.11.11 -kadira:blaze-layout@2.3.0 -kadira:dochead@1.5.0 -kadira:flow-router@2.12.1 -kenton:accounts-sandstorm@0.7.0 -konecty:mongo-counter@0.0.5_3 -lamhieu:meteorx@2.1.1 -lamhieu:unblock@1.0.0 -launch-screen@1.1.1 -livedata@1.0.18 -localstorage@1.2.0 -logging@1.1.20 -lucasantoniassi:accounts-lockout@1.0.0 -matb33:collection-hooks@0.9.1 -matteodem:easy-search@1.6.4 -mdg:meteor-apm-agent@3.2.5 -mdg:validation-error@0.5.1 -meteor@1.9.3 -meteor-base@1.4.0 -meteor-platform@1.2.6 -meteorhacks:aggregate@1.3.0 -meteorhacks:collection-utils@1.2.0 -meteorhacks:picker@1.0.3 -meteorhacks:subs-manager@1.6.4 -meteorspark:util@0.2.0 -minifier-css@1.4.3 -minifier-js@2.5.1 -minifiers@1.1.8-faster-rebuild.0 -minimongo@1.4.5 -mobile-status-bar@1.0.14 -modern-browsers@0.1.4 -modules@0.14.0 -modules-runtime@0.11.0 -momentjs:moment@2.24.0 -mongo@1.7.0 -mongo-decimal@0.1.1 -mongo-dev-server@1.1.0 -mongo-id@1.0.7 -mongo-livedata@1.0.12 -mousetrap:mousetrap@1.4.6_1 -mquandalle:autofocus@1.0.0 -mquandalle:collection-mutations@0.1.0 -mquandalle:jade@0.4.9 -mquandalle:jade-compiler@0.4.5 -mquandalle:jquery-textcomplete@0.8.0_1 -mquandalle:jquery-ui-drag-drop-sort@0.2.0 -mquandalle:moment@1.0.1 -mquandalle:mousetrap-bindglobal@0.0.1 -mquandalle:perfect-scrollbar@0.6.5_2 -msavin:usercache@1.8.0 -npm-bcrypt@0.9.3 -npm-mongo@3.2.0 -oauth@1.2.8 -oauth2@1.2.1 -observe-sequence@1.0.16 -ongoworks:speakingurl@1.1.0 -ordered-dict@1.1.0 -ostrio:cookies@2.5.0 -peerlibrary:assert@0.3.0 -peerlibrary:base-component@0.16.0 -peerlibrary:blaze-components@0.15.1 -peerlibrary:computed-field@0.10.0 -peerlibrary:reactive-field@0.6.0 -percolate:synced-cron@1.3.2 -promise@0.11.2 -raix:eventemitter@0.1.3 -raix:handlebar-helpers@0.2.5 -rajit:bootstrap3-datepicker@1.7.1_1 -random@1.1.0 -rate-limit@1.0.9 -reactive-dict@1.3.0 -reactive-var@1.0.11 -reload@1.3.0 -retry@1.1.0 -routepolicy@1.1.0 -rzymek:fullcalendar@3.8.0 -server-render@0.3.1 -service-configuration@1.0.11 -session@1.2.0 -sha@1.0.9 -shell-server@0.4.0 -simple:authenticate-user-by-token@1.0.1 -simple:json-routes@2.1.0 -simple:rest-accounts-password@1.1.2 -simple:rest-bearer-token-parser@1.0.1 -simple:rest-json-error-handler@1.0.1 -socket-stream-client@0.2.2 -softwarerero:accounts-t9n@1.3.11 -spacebars@1.0.15 -spacebars-compiler@1.1.3 -srp@1.0.12 -standard-minifier-css@1.5.4 -standard-minifier-js@2.5.2 -staringatlights:fast-render@3.2.0 -staringatlights:inject-data@2.3.0 -tap:i18n@1.8.2 -templates:tabs@2.3.0 -templating@1.3.2 -templating-compiler@1.3.3 -templating-runtime@1.3.2 -templating-tools@1.1.2 -tracker@1.2.0 -twbs:bootstrap@3.3.6 -ui@1.0.13 -underscore@1.0.10 -url@1.2.0 -useraccounts:core@1.14.2 -useraccounts:flow-routing@1.14.2 -useraccounts:unstyled@1.14.2 -verron:autosize@3.0.8 -webapp@1.7.5 -webapp-hashing@1.0.9 -wekan-accounts-cas@0.1.0 -wekan-accounts-oidc@1.0.10 -wekan-ldap@0.0.2 -wekan-markdown@1.0.7 -wekan-oidc@1.0.12 -wekan-scrollbar@3.1.3 -yasaricli:slugify@0.0.7 -zimme:active-route@2.3.2 diff --git a/.sandstorm-meteor-1.8/cfs_access-point.txt b/.sandstorm-meteor-1.8/cfs_access-point.txt deleted file mode 100644 index 8e3359d05..000000000 --- a/.sandstorm-meteor-1.8/cfs_access-point.txt +++ /dev/null @@ -1,914 +0,0 @@ -(function () { - -/* Imports */ -var Meteor = Package.meteor.Meteor; -var global = Package.meteor.global; -var meteorEnv = Package.meteor.meteorEnv; -var FS = Package['cfs:base-package'].FS; -var check = Package.check.check; -var Match = Package.check.Match; -var EJSON = Package.ejson.EJSON; -var HTTP = Package['cfs:http-methods'].HTTP; - -/* Package-scope variables */ -var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls; - -(function(){ - -/////////////////////////////////////////////////////////////////////// -// // -// packages/cfs_access-point/packages/cfs_access-point.js // -// // -/////////////////////////////////////////////////////////////////////// - // -(function () { - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// // -// packages/cfs:access-point/access-point-common.js // -// // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // -rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; // 1 -// Adjust the rootUrlPathPrefix if necessary // 2 -if (rootUrlPathPrefix.length > 0) { // 3 - if (rootUrlPathPrefix.slice(0, 1) !== '/') { // 4 - rootUrlPathPrefix = '/' + rootUrlPathPrefix; // 5 - } // 6 - if (rootUrlPathPrefix.slice(-1) === '/') { // 7 - rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1); // 8 - } // 9 -} // 10 - // 11 -// prepend ROOT_URL when isCordova // 12 -if (Meteor.isCordova) { // 13 - rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, ''); // 14 -} // 15 - // 16 -baseUrl = '/cfs'; // 17 -FS.HTTP = FS.HTTP || {}; // 18 - // 19 -// Note the upload URL so that client uploader packages know what it is // 20 -FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 21 - // 22 -/** // 23 - * @method FS.HTTP.setBaseUrl // 24 - * @public // 25 - * @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints. // 26 - * @returns {undefined} // 27 - */ // 28 -FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { // 29 - // 30 - // Adjust the baseUrl if necessary // 31 - if (newBaseUrl.slice(0, 1) !== '/') { // 32 - newBaseUrl = '/' + newBaseUrl; // 33 - } // 34 - if (newBaseUrl.slice(-1) === '/') { // 35 - newBaseUrl = newBaseUrl.slice(0, -1); // 36 - } // 37 - // 38 - // Update the base URL // 39 - baseUrl = newBaseUrl; // 40 - // 41 - // Change the upload URL so that client uploader packages know what it is // 42 - FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 43 - // 44 - // Remount URLs with the new baseUrl, unmounting the old, on the server only. // 45 - // If existingMountPoints is empty, then we haven't run the server startup // 46 - // code yet, so this new URL will be used at that point for the initial mount. // 47 - if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) { // 48 - mountUrls(); // 49 - } // 50 -}; // 51 - // 52 -/* // 53 - * FS.File extensions // 54 - */ // 55 - // 56 -/** // 57 - * @method FS.File.prototype.url Construct the file url // 58 - * @public // 59 - * @param {Object} [options] // 60 - * @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used. - * @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds. - * @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser. - * @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet. - * @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself. - * @param {String} [options.uploading=null] A URL to return while the file is being uploaded. // 66 - * @param {String} [options.storing=null] A URL to return while the file is being stored. // 67 - * @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store. - * // 69 - * Returns the HTTP URL for getting the file or its metadata. // 70 - */ // 71 -FS.File.prototype.url = function(options) { // 72 - var self = this; // 73 - options = options || {}; // 74 - options = FS.Utility.extend({ // 75 - store: null, // 76 - auth: null, // 77 - download: false, // 78 - metadata: false, // 79 - brokenIsFine: false, // 80 - uploading: null, // return this URL while uploading // 81 - storing: null, // return this URL while storing // 82 - filename: null // override the filename that is shown to the user // 83 - }, options.hash || options); // check for "hash" prop if called as helper // 84 - // 85 - // Primarily useful for displaying a temporary image while uploading an image // 86 - if (options.uploading && !self.isUploaded()) { // 87 - return options.uploading; // 88 - } // 89 - // 90 - if (self.isMounted()) { // 91 - // See if we've stored in the requested store yet // 92 - var storeName = options.store || self.collection.primaryStore.name; // 93 - if (!self.hasStored(storeName)) { // 94 - if (options.storing) { // 95 - return options.storing; // 96 - } else if (!options.brokenIsFine) { // 97 - // We want to return null if we know the URL will be a broken // 98 - // link because then we can avoid rendering broken links, broken // 99 - // images, etc. // 100 - return null; // 101 - } // 102 - } // 103 - // 104 - // Add filename to end of URL if we can determine one // 105 - var filename = options.filename || self.name({store: storeName}); // 106 - if (typeof filename === "string" && filename.length) { // 107 - filename = '/' + filename; // 108 - } else { // 109 - filename = ''; // 110 - } // 111 - // 112 - // TODO: Could we somehow figure out if the collection requires login? // 113 - var authToken = ''; // 114 - if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") { // 115 - if (options.auth !== false) { // 116 - // Add reactive deps on the user // 117 - Meteor.userId(); // 118 - // 119 - var authObject = { // 120 - authToken: Accounts._storedLoginToken() || '' // 121 - }; // 122 - // 123 - // If it's a number, we use that as the expiration time (in seconds) // 124 - if (options.auth === +options.auth) { // 125 - authObject.expiration = FS.HTTP.now() + options.auth * 1000; // 126 - } // 127 - // 128 - // Set the authToken // 129 - var authString = JSON.stringify(authObject); // 130 - authToken = FS.Utility.btoa(authString); // 131 - } // 132 - } else if (typeof options.auth === "string") { // 133 - // If the user supplies auth token the user will be responsible for // 134 - // updating // 135 - authToken = options.auth; // 136 - } // 137 - // 138 - // Construct query string // 139 - var params = {}; // 140 - if (authToken !== '') { // 141 - params.token = authToken; // 142 - } // 143 - if (options.download) { // 144 - params.download = true; // 145 - } // 146 - if (options.store) { // 147 - // We use options.store here instead of storeName because we want to omit the queryString // 148 - // whenever possible, allowing users to have "clean" URLs if they want. The server will // 149 - // assume the first store defined on the server, which means that we are assuming that // 150 - // the first on the client is also the first on the server. If that's not the case, the // 151 - // store option should be supplied. // 152 - params.store = options.store; // 153 - } // 154 - var queryString = FS.Utility.encodeParams(params); // 155 - if (queryString.length) { // 156 - queryString = '?' + queryString; // 157 - } // 158 - // 159 - // Determine which URL to use // 160 - var area; // 161 - if (options.metadata) { // 162 - area = '/record'; // 163 - } else { // 164 - area = '/files'; // 165 - } // 166 - // 167 - // Construct and return the http method url // 168 - return rootUrlPathPrefix + baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString; // 169 - } // 170 - // 171 -}; // 172 - // 173 - // 174 - // 175 -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -}).call(this); - - - - - - -(function () { - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// // -// packages/cfs:access-point/access-point-handlers.js // -// // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // -getHeaders = []; // 1 -getHeadersByCollection = {}; // 2 - // 3 -FS.HTTP.Handlers = {}; // 4 - // 5 -/** // 6 - * @method FS.HTTP.Handlers.Del // 7 - * @public // 8 - * @returns {any} response // 9 - * // 10 - * HTTP DEL request handler // 11 - */ // 12 -FS.HTTP.Handlers.Del = function httpDelHandler(ref) { // 13 - var self = this; // 14 - var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 15 - // 16 - // If DELETE request, validate with 'remove' allow/deny, delete the file, and return // 17 - FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId); // 18 - // 19 - /* // 20 - * From the DELETE spec: // 21 - * A successful response SHOULD be 200 (OK) if the response includes an // 22 - * entity describing the status, 202 (Accepted) if the action has not // 23 - * yet been enacted, or 204 (No Content) if the action has been enacted // 24 - * but the response does not include an entity. // 25 - */ // 26 - self.setStatusCode(200); // 27 - // 28 - return { // 29 - deleted: !!ref.file.remove() // 30 - }; // 31 -}; // 32 - // 33 -/** // 34 - * @method FS.HTTP.Handlers.GetList // 35 - * @public // 36 - * @returns {Object} response // 37 - * // 38 - * HTTP GET file list request handler // 39 - */ // 40 -FS.HTTP.Handlers.GetList = function httpGetListHandler() { // 41 - // Not Yet Implemented // 42 - // Need to check publications and return file list based on // 43 - // what user is allowed to see // 44 -}; // 45 - // 46 -/* // 47 - requestRange will parse the range set in request header - if not possible it // 48 - will throw fitting errors and autofill range for both partial and full ranges // 49 - // 50 - throws error or returns the object: // 51 - { // 52 - start // 53 - end // 54 - length // 55 - unit // 56 - partial // 57 - } // 58 -*/ // 59 -var requestRange = function(req, fileSize) { // 60 - if (req) { // 61 - if (req.headers) { // 62 - var rangeString = req.headers.range; // 63 - // 64 - // Make sure range is a string // 65 - if (rangeString === ''+rangeString) { // 66 - // 67 - // range will be in the format "bytes=0-32767" // 68 - var parts = rangeString.split('='); // 69 - var unit = parts[0]; // 70 - // 71 - // Make sure parts consists of two strings and range is of type "byte" // 72 - if (parts.length == 2 && unit == 'bytes') { // 73 - // Parse the range // 74 - var range = parts[1].split('-'); // 75 - var start = Number(range[0]); // 76 - var end = Number(range[1]); // 77 - // 78 - // Fix invalid ranges? // 79 - if (range[0] != start) start = 0; // 80 - if (range[1] != end || !end) end = fileSize - 1; // 81 - // 82 - // Make sure range consists of a start and end point of numbers and start is less than end // 83 - if (start < end) { // 84 - // 85 - var partSize = 0 - start + end + 1; // 86 - // 87 - // Return the parsed range // 88 - return { // 89 - start: start, // 90 - end: end, // 91 - length: partSize, // 92 - size: fileSize, // 93 - unit: unit, // 94 - partial: (partSize < fileSize) // 95 - }; // 96 - // 97 - } else { // 98 - throw new Meteor.Error(416, "Requested Range Not Satisfiable"); // 99 - } // 100 - // 101 - } else { // 102 - // The first part should be bytes // 103 - throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable"); // 104 - } // 105 - // 106 - } else { // 107 - // No range found // 108 - } // 109 - // 110 - } else { // 111 - // throw new Error('No request headers set for _parseRange function'); // 112 - } // 113 - } else { // 114 - throw new Error('No request object passed to _parseRange function'); // 115 - } // 116 - // 117 - return { // 118 - start: 0, // 119 - end: fileSize - 1, // 120 - length: fileSize, // 121 - size: fileSize, // 122 - unit: 'bytes', // 123 - partial: false // 124 - }; // 125 -}; // 126 - // 127 -/** // 128 - * @method FS.HTTP.Handlers.Get // 129 - * @public // 130 - * @returns {any} response // 131 - * // 132 - * HTTP GET request handler // 133 - */ // 134 -FS.HTTP.Handlers.Get = function httpGetHandler(ref) { // 135 - var self = this; // 136 - // Once we have the file, we can test allow/deny validators // 137 - // XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access? // 138 - FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/); // 139 - // 140 - var storeName = ref.storeName; // 141 - // 142 - // If no storeName was specified, use the first defined storeName // 143 - if (typeof storeName !== "string") { // 144 - // No store handed, we default to primary store // 145 - storeName = ref.collection.primaryStore.name; // 146 - } // 147 - // 148 - // Get the storage reference // 149 - var storage = ref.collection.storesLookup[storeName]; // 150 - // 151 - if (!storage) { // 152 - throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"'); // 153 - } // 154 - // 155 - // Get the file // 156 - var copyInfo = ref.file.copies[storeName]; // 157 - // 158 - if (!copyInfo) { // 159 - throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store'); // 160 - } // 161 - // 162 - // Set the content type for file // 163 - if (typeof copyInfo.type === "string") { // 164 - self.setContentType(copyInfo.type); // 165 - } else { // 166 - self.setContentType('application/octet-stream'); // 167 - } // 168 - // 169 - // Add 'Content-Disposition' header if requested a download/attachment URL // 170 - if (typeof ref.download !== "undefined") { // 171 - var filename = ref.filename || copyInfo.name; // 172 - self.addHeader('Content-Disposition', 'attachment; filename="' + filename + '"'); // 173 - } else { // 174 - self.addHeader('Content-Disposition', 'inline'); // 175 - } // 176 - // 177 - // Get the contents range from request // 178 - var range = requestRange(self.request, copyInfo.size); // 179 - // 180 - // Some browsers cope better if the content-range header is // 181 - // still included even for the full file being returned. // 182 - self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); // 183 - // 184 - // If a chunk/range was requested instead of the whole file, serve that' // 185 - if (range.partial) { // 186 - self.setStatusCode(206, 'Partial Content'); // 187 - } else { // 188 - self.setStatusCode(200, 'OK'); // 189 - } // 190 - // 191 - // Add any other global custom headers and collection-specific custom headers // 192 - FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) { // 193 - self.addHeader(header[0], header[1]); // 194 - }); // 195 - // 196 - // Inform clients about length (or chunk length in case of ranges) // 197 - self.addHeader('Content-Length', range.length); // 198 - // 199 - // Last modified header (updatedAt from file info) // 200 - self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString()); // 201 - // 202 - // Inform clients that we accept ranges for resumable chunked downloads // 203 - self.addHeader('Accept-Ranges', range.unit); // 204 - // 205 - if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); - // 207 - var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end}); // 208 - // 209 - readStream.on('error', function(err) { // 210 - // Send proper error message on get error // 211 - if (err.message && err.statusCode) { // 212 - self.Error(new Meteor.Error(err.statusCode, err.message)); // 213 - } else { // 214 - self.Error(new Meteor.Error(503, 'Service unavailable')); // 215 - } // 216 - }); // 217 - // 218 - readStream.pipe(self.createWriteStream()); // 219 -}; // 220 - -const originalHandler = FS.HTTP.Handlers.Get; -FS.HTTP.Handlers.Get = function (ref) { -//console.log(ref.filename); - try { - var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase(); - - if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) { - ref.filename = encodeURIComponent(ref.filename); - } else if(userAgent.indexOf('firefox') >= 0) { - ref.filename = new Buffer(ref.filename).toString('binary'); - } else { - /* safari*/ - ref.filename = new Buffer(ref.filename).toString('binary'); - } - } catch (ex){ - ref.filename = 'tempfix'; - } - return originalHandler.call(this, ref); -}; - // 221 -/** // 222 - * @method FS.HTTP.Handlers.PutInsert // 223 - * @public // 224 - * @returns {Object} response object with _id property // 225 - * // 226 - * HTTP PUT file insert request handler // 227 - */ // 228 -FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { // 229 - var self = this; // 230 - var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 231 - // 232 - FS.debug && console.log("HTTP PUT (insert) handler"); // 233 - // 234 - // Create the nice FS.File // 235 - var fileObj = new FS.File(); // 236 - // 237 - // Set its name // 238 - fileObj.name(opts.filename || null); // 239 - // 240 - // Attach the readstream as the file's data // 241 - fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'}); - // 243 - // Validate with insert allow/deny // 244 - FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId); // 245 - // 246 - // Insert file into collection, triggering readStream storage // 247 - ref.collection.insert(fileObj); // 248 - // 249 - // Send response // 250 - self.setStatusCode(200); // 251 - // 252 - // Return the new file id // 253 - return {_id: fileObj._id}; // 254 -}; // 255 - // 256 -/** // 257 - * @method FS.HTTP.Handlers.PutUpdate // 258 - * @public // 259 - * @returns {Object} response object with _id and chunk properties // 260 - * // 261 - * HTTP PUT file update chunk request handler // 262 - */ // 263 -FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { // 264 - var self = this; // 265 - var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 266 - // 267 - var chunk = parseInt(opts.chunk, 10); // 268 - if (isNaN(chunk)) chunk = 0; // 269 - // 270 - FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk); // 271 - // 272 - // Validate with insert allow/deny; also mounts and retrieves the file // 273 - FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId); // 274 - // 275 - self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) ); // 276 - // 277 - // Send response // 278 - self.setStatusCode(200); // 279 - // 280 - return { _id: ref.file._id, chunk: chunk }; // 281 -}; // 282 - // 283 -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -}).call(this); - - - - - - -(function () { - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// // -// packages/cfs:access-point/access-point-server.js // -// // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // -var path = Npm.require("path"); // 1 - // 2 -HTTP.publishFormats({ // 3 - fileRecordFormat: function (input) { // 4 - // Set the method scope content type to json // 5 - this.setContentType('application/json'); // 6 - if (FS.Utility.isArray(input)) { // 7 - return EJSON.stringify(FS.Utility.map(input, function (obj) { // 8 - return FS.Utility.cloneFileRecord(obj); // 9 - })); // 10 - } else { // 11 - return EJSON.stringify(FS.Utility.cloneFileRecord(input)); // 12 - } // 13 - } // 14 -}); // 15 - // 16 -/** // 17 - * @method FS.HTTP.setHeadersForGet // 18 - * @public // 19 - * @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value. - * @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections. - * @returns {undefined} // 22 - */ // 23 -FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { // 24 - if (typeof collections === "string") { // 25 - collections = [collections]; // 26 - } // 27 - if (collections) { // 28 - FS.Utility.each(collections, function(collectionName) { // 29 - getHeadersByCollection[collectionName] = headers || []; // 30 - }); // 31 - } else { // 32 - getHeaders = headers || []; // 33 - } // 34 -}; // 35 - // 36 -/** // 37 - * @method FS.HTTP.publish // 38 - * @public // 39 - * @param {FS.Collection} collection // 40 - * @param {Function} func - Publish function that returns a cursor. // 41 - * @returns {undefined} // 42 - * // 43 - * Publishes all documents returned by the cursor at a GET URL // 44 - * with the format baseUrl/record/collectionName. The publish // 45 - * function `this` is similar to normal `Meteor.publish`. // 46 - */ // 47 -FS.HTTP.publish = function fsHttpPublish(collection, func) { // 48 - var name = baseUrl + '/record/' + collection.name; // 49 - // Mount collection listing URL using http-publish package // 50 - HTTP.publish({ // 51 - name: name, // 52 - defaultFormat: 'fileRecordFormat', // 53 - collection: collection, // 54 - collectionGet: true, // 55 - collectionPost: false, // 56 - documentGet: true, // 57 - documentPut: false, // 58 - documentDelete: false // 59 - }, func); // 60 - // 61 - FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n'); // 62 -}; // 63 - // 64 -/** // 65 - * @method FS.HTTP.unpublish // 66 - * @public // 67 - * @param {FS.Collection} collection // 68 - * @returns {undefined} // 69 - * // 70 - * Unpublishes a restpoint created by a call to `FS.HTTP.publish` // 71 - */ // 72 -FS.HTTP.unpublish = function fsHttpUnpublish(collection) { // 73 - // Mount collection listing URL using http-publish package // 74 - HTTP.unpublish(baseUrl + '/record/' + collection.name); // 75 -}; // 76 - // 77 -_existingMountPoints = {}; // 78 - // 79 -/** // 80 - * @method defaultSelectorFunction // 81 - * @private // 82 - * @returns { collection, file } // 83 - * // 84 - * This is the default selector function // 85 - */ // 86 -var defaultSelectorFunction = function() { // 87 - var self = this; // 88 - // Selector function // 89 - // // 90 - // This function will have to return the collection and the // 91 - // file. If file not found undefined is returned - if null is returned the // 92 - // search was not possible // 93 - var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 94 - // 95 - // Get the collection name from the url // 96 - var collectionName = opts.collectionName; // 97 - // 98 - // Get the id from the url // 99 - var id = opts.id; // 100 - // 101 - // Get the collection // 102 - var collection = FS._collections[collectionName]; // 103 - // 104 - // Get the file if possible else return null // 105 - var file = (id && collection)? collection.findOne({ _id: id }): null; // 106 - // 107 - // Return the collection and the file // 108 - return { // 109 - collection: collection, // 110 - file: file, // 111 - storeName: opts.store, // 112 - download: opts.download, // 113 - filename: opts.filename // 114 - }; // 115 -}; // 116 - // 117 -/* // 118 - * @method FS.HTTP.mount // 119 - * @public // 120 - * @param {array of string} mountPoints mount points to map rest functinality on // 121 - * @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with // 122 - * // 123 -*/ // 124 -FS.HTTP.mount = function(mountPoints, selector_f) { // 125 - // We take mount points as an array and we get a selector function // 126 - var selectorFunction = selector_f || defaultSelectorFunction; // 127 - // 128 - var accessPoint = { // 129 - 'stream': true, // 130 - 'auth': expirationAuth, // 131 - 'post': function(data) { // 132 - // Use the selector for finding the collection and file reference // 133 - var ref = selectorFunction.call(this); // 134 - // 135 - // We dont support post - this would be normal insert eg. of filerecord? // 136 - throw new Meteor.Error(501, "Not implemented", "Post is not supported"); // 137 - }, // 138 - 'put': function(data) { // 139 - // Use the selector for finding the collection and file reference // 140 - var ref = selectorFunction.call(this); // 141 - // 142 - // Make sure we have a collection reference // 143 - if (!ref.collection) // 144 - throw new Meteor.Error(404, "Not Found", "No collection found"); // 145 - // 146 - // Make sure we have a file reference // 147 - if (ref.file === null) { // 148 - // No id supplied so we will create a new FS.File instance and // 149 - // insert the supplied data. // 150 - return FS.HTTP.Handlers.PutInsert.apply(this, [ref]); // 151 - } else { // 152 - if (ref.file) { // 153 - return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]); // 154 - } else { // 155 - throw new Meteor.Error(404, "Not Found", 'No file found'); // 156 - } // 157 - } // 158 - }, // 159 - 'get': function(data) { // 160 - // Use the selector for finding the collection and file reference // 161 - var ref = selectorFunction.call(this); // 162 - // 163 - // Make sure we have a collection reference // 164 - if (!ref.collection) // 165 - throw new Meteor.Error(404, "Not Found", "No collection found"); // 166 - // 167 - // Make sure we have a file reference // 168 - if (ref.file === null) { // 169 - // No id supplied so we will return the published list of files ala // 170 - // http.publish in json format // 171 - return FS.HTTP.Handlers.GetList.apply(this, [ref]); // 172 - } else { // 173 - if (ref.file) { // 174 - return FS.HTTP.Handlers.Get.apply(this, [ref]); // 175 - } else { // 176 - throw new Meteor.Error(404, "Not Found", 'No file found'); // 177 - } // 178 - } // 179 - }, // 180 - 'delete': function(data) { // 181 - // Use the selector for finding the collection and file reference // 182 - var ref = selectorFunction.call(this); // 183 - // 184 - // Make sure we have a collection reference // 185 - if (!ref.collection) // 186 - throw new Meteor.Error(404, "Not Found", "No collection found"); // 187 - // 188 - // Make sure we have a file reference // 189 - if (ref.file) { // 190 - return FS.HTTP.Handlers.Del.apply(this, [ref]); // 191 - } else { // 192 - throw new Meteor.Error(404, "Not Found", 'No file found'); // 193 - } // 194 - } // 195 - }; // 196 - // 197 - var accessPoints = {}; // 198 - // 199 - // Add debug message // 200 - FS.debug && console.log('Registered HTTP method URLs:'); // 201 - // 202 - FS.Utility.each(mountPoints, function(mountPoint) { // 203 - // Couple mountpoint and accesspoint // 204 - accessPoints[mountPoint] = accessPoint; // 205 - // Remember our mountpoints // 206 - _existingMountPoints[mountPoint] = mountPoint; // 207 - // Add debug message // 208 - FS.debug && console.log(mountPoint); // 209 - }); // 210 - // 211 - // XXX: HTTP:methods should unmount existing mounts in case of overwriting? // 212 - HTTP.methods(accessPoints); // 213 - // 214 -}; // 215 - // 216 -/** // 217 - * @method FS.HTTP.unmount // 218 - * @public // 219 - * @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted // 220 - * // 221 - */ // 222 -FS.HTTP.unmount = function(mountPoints) { // 223 - // The mountPoints is optional, can be string or array if undefined then // 224 - // _existingMountPoints will be used // 225 - var unmountList; // 226 - // Container for the mount points to unmount // 227 - var unmountPoints = {}; // 228 - // 229 - if (typeof mountPoints === 'undefined') { // 230 - // Use existing mount points - unmount all // 231 - unmountList = _existingMountPoints; // 232 - } else if (mountPoints === ''+mountPoints) { // 233 - // Got a string // 234 - unmountList = [mountPoints]; // 235 - } else if (mountPoints.length) { // 236 - // Got an array // 237 - unmountList = mountPoints; // 238 - } // 239 - // 240 - // If we have a list to unmount // 241 - if (unmountList) { // 242 - // Iterate over each item // 243 - FS.Utility.each(unmountList, function(mountPoint) { // 244 - // Check _existingMountPoints to make sure the mount point exists in our // 245 - // context / was created by the FS.HTTP.mount // 246 - if (_existingMountPoints[mountPoint]) { // 247 - // Mark as unmount // 248 - unmountPoints[mountPoint] = false; // 249 - // Release // 250 - delete _existingMountPoints[mountPoint]; // 251 - } // 252 - }); // 253 - FS.debug && console.log('FS.HTTP.unmount:'); // 254 - FS.debug && console.log(unmountPoints); // 255 - // Complete unmount // 256 - HTTP.methods(unmountPoints); // 257 - } // 258 -}; // 259 - // 260 -// ### FS.Collection maps on HTTP pr. default on the following restpoints: // 261 -// * // 262 -// baseUrl + '/files/:collectionName/:id/:filename', // 263 -// baseUrl + '/files/:collectionName/:id', // 264 -// baseUrl + '/files/:collectionName' // 265 -// // 266 -// Change/ replace the existing mount point by: // 267 -// ```js // 268 -// // unmount all existing // 269 -// FS.HTTP.unmount(); // 270 -// // Create new mount point // 271 -// FS.HTTP.mount([ // 272 -// '/cfs/files/:collectionName/:id/:filename', // 273 -// '/cfs/files/:collectionName/:id', // 274 -// '/cfs/files/:collectionName' // 275 -// ]); // 276 -// ``` // 277 -// // 278 -mountUrls = function mountUrls() { // 279 - // We unmount first in case we are calling this a second time // 280 - FS.HTTP.unmount(); // 281 - // 282 - FS.HTTP.mount([ // 283 - baseUrl + '/files/:collectionName/:id/:filename', // 284 - baseUrl + '/files/:collectionName/:id', // 285 - baseUrl + '/files/:collectionName' // 286 - ]); // 287 -}; // 288 - // 289 -// Returns the userId from URL token // 290 -var expirationAuth = function expirationAuth() { // 291 - var self = this; // 292 - // 293 - // Read the token from '/hello?token=base64' // 294 - var encodedToken = self.query.token; // 295 - // 296 - FS.debug && console.log("token: "+encodedToken); // 297 - // 298 - if (!encodedToken || !Meteor.users) return false; // 299 - // 300 - // Check the userToken before adding it to the db query // 301 - // Set the this.userId // 302 - var tokenString = FS.Utility.atob(encodedToken); // 303 - // 304 - var tokenObject; // 305 - try { // 306 - tokenObject = JSON.parse(tokenString); // 307 - } catch(err) { // 308 - throw new Meteor.Error(400, 'Bad Request'); // 309 - } // 310 - // 311 - // XXX: Do some check here of the object // 312 - var userToken = tokenObject.authToken; // 313 - if (userToken !== ''+userToken) { // 314 - throw new Meteor.Error(400, 'Bad Request'); // 315 - } // 316 - // 317 - // If we have an expiration token we should check that it's still valid // 318 - if (tokenObject.expiration != null) { // 319 - // check if its too old // 320 - var now = Date.now(); // 321 - if (tokenObject.expiration < now) { // 322 - FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now); // 323 - throw new Meteor.Error(500, 'Expired token'); // 324 - } // 325 - } // 326 - // 327 - // We are not on a secure line - so we have to look up the user... // 328 - var user = Meteor.users.findOne({ // 329 - $or: [ // 330 - {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)}, // 331 - {'services.resume.loginTokens.token': userToken} // 332 - ] // 333 - }); // 334 - // 335 - // Set the userId in the scope // 336 - return user && user._id; // 337 -}; // 338 - // 339 -HTTP.methods( // 340 - {'/cfs/servertime': { // 341 - get: function(data) { // 342 - return Date.now().toString(); // 343 - } // 344 - } // 345 -}); // 346 - // 347 -// Unify client / server api // 348 -FS.HTTP.now = function() { // 349 - return Date.now(); // 350 -}; // 351 - // 352 -// Start up the basic mount points // 353 -Meteor.startup(function () { // 354 - mountUrls(); // 355 -}); // 356 - // 357 -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -}).call(this); - -/////////////////////////////////////////////////////////////////////// - -}).call(this); - - -/* Exports */ -if (typeof Package === 'undefined') Package = {}; -Package['cfs:access-point'] = {}; - -})(); diff --git a/.sandstorm-meteor-1.8/export.js b/.sandstorm-meteor-1.8/export.js deleted file mode 100644 index cc979ce01..000000000 --- a/.sandstorm-meteor-1.8/export.js +++ /dev/null @@ -1,238 +0,0 @@ -/* global JsonRoutes */ -if (Meteor.isServer) { - // todo XXX once we have a real API in place, move that route there - // todo XXX also share the route definition between the client and the server - // so that we could use something like - // `ApiRoutes.path('boards/export', boardId)`` - // on the client instead of copy/pasting the route path manually between the - // client and the server. - /** - * @operation export - * @tag Boards - * - * @summary This route is used to export the board. - * - * @description If user is already logged-in, pass loginToken as param - * "authToken": '/api/boards/:boardId/export?authToken=:token' - * - * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ - * for detailed explanations - * - * @param {string} boardId the ID of the board we are exporting - * @param {string} authToken the loginToken - */ - JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { - const boardId = req.params.boardId; - let user = null; - - const loginToken = req.query.authToken; - if (loginToken) { - const hashToken = Accounts._hashLoginToken(loginToken); - user = Meteor.users.findOne({ - 'services.resume.loginTokens.hashedToken': hashToken, - }); - } else if (!Meteor.settings.public.sandstorm) { - Authentication.checkUserId(req.userId); - user = Users.findOne({ _id: req.userId, isAdmin: true }); - } - - const exporter = new Exporter(boardId); - if (exporter.canExport(user)) { - JsonRoutes.sendResult(res, { - code: 200, - data: exporter.build(), - }); - } else { - // we could send an explicit error message, but on the other hand the only - // way to get there is by hacking the UI so let's keep it raw. - JsonRoutes.sendResult(res, 403); - } - }); -} - -// exporter maybe is broken since Gridfs introduced, add fs and path - -export class Exporter { - constructor(boardId) { - this._boardId = boardId; - } - - build() { - const fs = Npm.require('fs'); - const os = Npm.require('os'); - const path = Npm.require('path'); - - const byBoard = { boardId: this._boardId }; - const byBoardNoLinked = { - boardId: this._boardId, - linkedId: { $in: ['', null] }, - }; - // we do not want to retrieve boardId in related elements - const noBoardId = { - fields: { - boardId: 0, - }, - }; - const result = { - _format: 'wekan-board-1.0.0', - }; - _.extend( - result, - Boards.findOne(this._boardId, { - fields: { - stars: 0, - }, - }), - ); - result.lists = Lists.find(byBoard, noBoardId).fetch(); - result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); - result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); - result.customFields = CustomFields.find( - { boardIds: { $in: [this.boardId] } }, - { fields: { boardId: 0 } }, - ).fetch(); - result.comments = CardComments.find(byBoard, noBoardId).fetch(); - result.activities = Activities.find(byBoard, noBoardId).fetch(); - result.rules = Rules.find(byBoard, noBoardId).fetch(); - result.checklists = []; - result.checklistItems = []; - result.subtaskItems = []; - result.triggers = []; - result.actions = []; - result.cards.forEach(card => { - result.checklists.push( - ...Checklists.find({ - cardId: card._id, - }).fetch(), - ); - result.checklistItems.push( - ...ChecklistItems.find({ - cardId: card._id, - }).fetch(), - ); - result.subtaskItems.push( - ...Cards.find({ - parentId: card._id, - }).fetch(), - ); - }); - result.rules.forEach(rule => { - result.triggers.push( - ...Triggers.find( - { - _id: rule.triggerId, - }, - noBoardId, - ).fetch(), - ); - result.actions.push( - ...Actions.find( - { - _id: rule.actionId, - }, - noBoardId, - ).fetch(), - ); - }); - - // [Old] for attachments we only export IDs and absolute url to original doc - // [New] Encode attachment to base64 - const getBase64Data = function(doc, callback) { - let buffer = new Buffer(0); - // callback has the form function (err, res) {} - const tmpFile = path.join( - os.tmpdir(), - `tmpexport${process.pid}${Math.random()}`, - ); - const tmpWriteable = fs.createWriteStream(tmpFile); - const readStream = doc.createReadStream(); - readStream.on('data', function(chunk) { - buffer = Buffer.concat([buffer, chunk]); - }); - readStream.on('error', function(err) { - callback(err, null); - }); - readStream.on('end', function() { - // done - fs.unlink(tmpFile, () => { - //ignored - }); - callback(null, buffer.toString('base64')); - }); - readStream.pipe(tmpWriteable); - }; - const getBase64DataSync = Meteor.wrapAsync(getBase64Data); - result.attachments = Attachments.find(byBoard) - .fetch() - .map(attachment => { - return { - _id: attachment._id, - cardId: attachment.cardId, - // url: FlowRouter.url(attachment.url()), - file: getBase64DataSync(attachment), - name: attachment.original.name, - type: attachment.original.type, - }; - }); - - // we also have to export some user data - as the other elements only - // include id but we have to be careful: - // 1- only exports users that are linked somehow to that board - // 2- do not export any sensitive information - const users = {}; - result.members.forEach(member => { - users[member.userId] = true; - }); - result.lists.forEach(list => { - users[list.userId] = true; - }); - result.cards.forEach(card => { - users[card.userId] = true; - if (card.members) { - card.members.forEach(memberId => { - users[memberId] = true; - }); - } - }); - result.comments.forEach(comment => { - users[comment.userId] = true; - }); - result.activities.forEach(activity => { - users[activity.userId] = true; - }); - result.checklists.forEach(checklist => { - users[checklist.userId] = true; - }); - const byUserIds = { - _id: { - $in: Object.getOwnPropertyNames(users), - }, - }; - // we use whitelist to be sure we do not expose inadvertently - // some secret fields that gets added to User later. - const userFields = { - fields: { - _id: 1, - username: 1, - 'profile.fullname': 1, - 'profile.initials': 1, - 'profile.avatarUrl': 1, - }, - }; - result.users = Users.find(byUserIds, userFields) - .fetch() - .map(user => { - // user avatar is stored as a relative url, we export absolute - if ((user.profile || {}).avatarUrl) { - user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); - } - return user; - }); - return result; - } - - canExport(user) { - const board = Boards.findOne(this._boardId); - return board && board.isVisibleBy(user); - } -} diff --git a/.sandstorm-meteor-1.8/ldap.js b/.sandstorm-meteor-1.8/ldap.js deleted file mode 100644 index 3b9638230..000000000 --- a/.sandstorm-meteor-1.8/ldap.js +++ /dev/null @@ -1,640 +0,0 @@ -import ldapjs from 'ldapjs'; -import util from 'util'; -import Bunyan from 'bunyan'; -import { log_debug, log_info, log_warn, log_error } from './logger'; - -export default class LDAP { - constructor() { - this.ldapjs = ldapjs; - - this.connected = false; - - this.options = { - host: this.constructor.settings_get('LDAP_HOST'), - port: this.constructor.settings_get('LDAP_PORT'), - Reconnect: this.constructor.settings_get('LDAP_RECONNECT'), - timeout: this.constructor.settings_get('LDAP_TIMEOUT'), - connect_timeout: this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'), - idle_timeout: this.constructor.settings_get('LDAP_IDLE_TIMEOUT'), - encryption: this.constructor.settings_get('LDAP_ENCRYPTION'), - ca_cert: this.constructor.settings_get('LDAP_CA_CERT'), - reject_unauthorized: - this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false, - Authentication: this.constructor.settings_get('LDAP_AUTHENTIFICATION'), - Authentication_UserDN: this.constructor.settings_get( - 'LDAP_AUTHENTIFICATION_USERDN', - ), - Authentication_Password: this.constructor.settings_get( - 'LDAP_AUTHENTIFICATION_PASSWORD', - ), - Authentication_Fallback: this.constructor.settings_get( - 'LDAP_LOGIN_FALLBACK', - ), - BaseDN: this.constructor.settings_get('LDAP_BASEDN'), - Internal_Log_Level: this.constructor.settings_get('INTERNAL_LOG_LEVEL'), - User_Authentication: this.constructor.settings_get( - 'LDAP_USER_AUTHENTICATION', - ), - User_Authentication_Field: this.constructor.settings_get( - 'LDAP_USER_AUTHENTICATION_FIELD', - ), - User_Attributes: this.constructor.settings_get('LDAP_USER_ATTRIBUTES'), - User_Search_Filter: this.constructor.settings_get( - 'LDAP_USER_SEARCH_FILTER', - ), - User_Search_Scope: this.constructor.settings_get( - 'LDAP_USER_SEARCH_SCOPE', - ), - User_Search_Field: this.constructor.settings_get( - 'LDAP_USER_SEARCH_FIELD', - ), - Search_Page_Size: this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'), - Search_Size_Limit: this.constructor.settings_get( - 'LDAP_SEARCH_SIZE_LIMIT', - ), - group_filter_enabled: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_ENABLE', - ), - group_filter_object_class: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_OBJECTCLASS', - ), - group_filter_group_id_attribute: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE', - ), - group_filter_group_member_attribute: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE', - ), - group_filter_group_member_format: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT', - ), - group_filter_group_name: this.constructor.settings_get( - 'LDAP_GROUP_FILTER_GROUP_NAME', - ), - }; - } - - static settings_get(name, ...args) { - let value = process.env[name]; - if (value !== undefined) { - if (value === 'true' || value === 'false') { - value = JSON.parse(value); - } else if (value !== '' && !isNaN(value)) { - value = Number(value); - } - return value; - } else { - log_warn(`Lookup for unset variable: ${name}`); - } - } - - connectSync(...args) { - if (!this._connectSync) { - this._connectSync = Meteor.wrapAsync(this.connectAsync, this); - } - return this._connectSync(...args); - } - - searchAllSync(...args) { - if (!this._searchAllSync) { - this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this); - } - return this._searchAllSync(...args); - } - - connectAsync(callback) { - log_info('Init setup'); - - let replied = false; - - const connectionOptions = { - url: `${this.options.host}:${this.options.port}`, - timeout: this.options.timeout, - connectTimeout: this.options.connect_timeout, - idleTimeout: this.options.idle_timeout, - reconnect: this.options.Reconnect, - }; - - if (this.options.Internal_Log_Level !== 'disabled') { - connectionOptions.log = new Bunyan({ - name: 'ldapjs', - component: 'client', - stream: process.stderr, - level: this.options.Internal_Log_Level, - }); - } - - const tlsOptions = { - rejectUnauthorized: this.options.reject_unauthorized, - }; - - if (this.options.ca_cert && this.options.ca_cert !== '') { - // Split CA cert into array of strings - const chainLines = this.constructor - .settings_get('LDAP_CA_CERT') - .split('\n'); - let cert = []; - const ca = []; - chainLines.forEach(line => { - cert.push(line); - if (line.match(/-END CERTIFICATE-/)) { - ca.push(cert.join('\n')); - cert = []; - } - }); - tlsOptions.ca = ca; - } - - if (this.options.encryption === 'ssl') { - connectionOptions.url = `ldaps://${connectionOptions.url}`; - connectionOptions.tlsOptions = tlsOptions; - } else { - connectionOptions.url = `ldap://${connectionOptions.url}`; - } - - log_info('Connecting', connectionOptions.url); - log_debug(`connectionOptions${util.inspect(connectionOptions)}`); - - this.client = ldapjs.createClient(connectionOptions); - - this.bindSync = Meteor.wrapAsync(this.client.bind, this.client); - - this.client.on('error', error => { - log_error('connection', error); - if (replied === false) { - replied = true; - callback(error, null); - } - }); - - this.client.on('idle', () => { - log_info('Idle'); - this.disconnect(); - }); - - this.client.on('close', () => { - log_info('Closed'); - }); - - if (this.options.encryption === 'tls') { - // Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0). - // https://github.com/RocketChat/Rocket.Chat/issues/2035 - // https://github.com/mcavage/node-ldapjs/issues/349 - tlsOptions.host = this.options.host; - - log_info('Starting TLS'); - log_debug('tlsOptions', tlsOptions); - - this.client.starttls(tlsOptions, null, (error, response) => { - if (error) { - log_error('TLS connection', error); - if (replied === false) { - replied = true; - callback(error, null); - } - return; - } - - log_info('TLS connected'); - this.connected = true; - if (replied === false) { - replied = true; - callback(null, response); - } - }); - } else { - this.client.on('connect', response => { - log_info('LDAP connected'); - this.connected = true; - if (replied === false) { - replied = true; - callback(null, response); - } - }); - } - - setTimeout(() => { - if (replied === false) { - log_error('connection time out', connectionOptions.connectTimeout); - replied = true; - callback(new Error('Timeout')); - } - }, connectionOptions.connectTimeout); - } - - getUserFilter(username) { - const filter = []; - - if (this.options.User_Search_Filter !== '') { - if (this.options.User_Search_Filter[0] === '(') { - filter.push(`${this.options.User_Search_Filter}`); - } else { - filter.push(`(${this.options.User_Search_Filter})`); - } - } - - const usernameFilter = this.options.User_Search_Field.split(',').map( - item => `(${item}=${username})`, - ); - - if (usernameFilter.length === 0) { - log_error('LDAP_LDAP_User_Search_Field not defined'); - } else if (usernameFilter.length === 1) { - filter.push(`${usernameFilter[0]}`); - } else { - filter.push(`(|${usernameFilter.join('')})`); - } - - return `(&${filter.join('')})`; - } - - bindUserIfNecessary(username, password) { - if (this.domainBinded === true) { - return; - } - - if (!this.options.User_Authentication) { - return; - } - - if (!this.options.BaseDN) throw new Error('BaseDN is not provided'); - - const userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`; - - this.bindSync(userDn, password); - this.domainBinded = true; - } - - bindIfNecessary() { - if (this.domainBinded === true) { - return; - } - - if (this.options.Authentication !== true) { - return; - } - - log_info('Binding UserDN', this.options.Authentication_UserDN); - - this.bindSync( - this.options.Authentication_UserDN, - this.options.Authentication_Password, - ); - this.domainBinded = true; - } - - searchUsersSync(username, page) { - this.bindIfNecessary(); - const searchOptions = { - filter: this.getUserFilter(username), - scope: this.options.User_Search_Scope || 'sub', - sizeLimit: this.options.Search_Size_Limit, - }; - - if (!!this.options.User_Attributes) - searchOptions.attributes = this.options.User_Attributes.split(','); - - if (this.options.Search_Page_Size > 0) { - searchOptions.paged = { - pageSize: this.options.Search_Page_Size, - pagePause: !!page, - }; - } - - log_info('Searching user', username); - log_debug('searchOptions', searchOptions); - log_debug('BaseDN', this.options.BaseDN); - - if (page) { - return this.searchAllPaged(this.options.BaseDN, searchOptions, page); - } - - return this.searchAllSync(this.options.BaseDN, searchOptions); - } - - getUserByIdSync(id, attribute) { - this.bindIfNecessary(); - - const Unique_Identifier_Field = this.constructor - .settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD') - .split(','); - - let filter; - - if (attribute) { - filter = new this.ldapjs.filters.EqualityFilter({ - attribute, - value: new Buffer(id, 'hex'), - }); - } else { - const filters = []; - Unique_Identifier_Field.forEach(item => { - filters.push( - new this.ldapjs.filters.EqualityFilter({ - attribute: item, - value: new Buffer(id, 'hex'), - }), - ); - }); - - filter = new this.ldapjs.filters.OrFilter({ filters }); - } - - const searchOptions = { - filter, - scope: 'sub', - }; - - log_info('Searching by id', id); - log_debug('search filter', searchOptions.filter.toString()); - log_debug('BaseDN', this.options.BaseDN); - - const result = this.searchAllSync(this.options.BaseDN, searchOptions); - - if (!Array.isArray(result) || result.length === 0) { - return; - } - - if (result.length > 1) { - log_error('Search by id', id, 'returned', result.length, 'records'); - } - - return result[0]; - } - - getUserByUsernameSync(username) { - this.bindIfNecessary(); - - const searchOptions = { - filter: this.getUserFilter(username), - scope: this.options.User_Search_Scope || 'sub', - }; - - log_info('Searching user', username); - log_debug('searchOptions', searchOptions); - log_debug('BaseDN', this.options.BaseDN); - - const result = this.searchAllSync(this.options.BaseDN, searchOptions); - - if (!Array.isArray(result) || result.length === 0) { - return; - } - - if (result.length > 1) { - log_error( - 'Search by username', - username, - 'returned', - result.length, - 'records', - ); - } - - return result[0]; - } - - getUserGroups(username, ldapUser) { - if (!this.options.group_filter_enabled) { - return true; - } - - const filter = ['(&']; - - if (this.options.group_filter_object_class !== '') { - filter.push(`(objectclass=${this.options.group_filter_object_class})`); - } - - if (this.options.group_filter_group_member_attribute !== '') { - const format_value = - ldapUser[this.options.group_filter_group_member_format]; - if (format_value) { - filter.push( - `(${this.options.group_filter_group_member_attribute}=${format_value})`, - ); - } - } - - filter.push(')'); - - const searchOptions = { - filter: filter.join('').replace(/#{username}/g, username), - scope: 'sub', - }; - - log_debug('Group list filter LDAP:', searchOptions.filter); - - const result = this.searchAllSync(this.options.BaseDN, searchOptions); - - if (!Array.isArray(result) || result.length === 0) { - return []; - } - - const grp_identifier = this.options.group_filter_group_id_attribute || 'cn'; - const groups = []; - result.map(item => { - groups.push(item[grp_identifier]); - }); - log_debug(`Groups: ${groups.join(', ')}`); - return groups; - } - - isUserInGroup(username, ldapUser) { - if (!this.options.group_filter_enabled) { - return true; - } - - const grps = this.getUserGroups(username, ldapUser); - - const filter = ['(&']; - - if (this.options.group_filter_object_class !== '') { - filter.push(`(objectclass=${this.options.group_filter_object_class})`); - } - - if (this.options.group_filter_group_member_attribute !== '') { - const format_value = - ldapUser[this.options.group_filter_group_member_format]; - if (format_value) { - filter.push( - `(${this.options.group_filter_group_member_attribute}=${format_value})`, - ); - } - } - - if (this.options.group_filter_group_id_attribute !== '') { - filter.push( - `(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`, - ); - } - filter.push(')'); - - const searchOptions = { - filter: filter.join('').replace(/#{username}/g, username), - scope: 'sub', - }; - - log_debug('Group filter LDAP:', searchOptions.filter); - - const result = this.searchAllSync(this.options.BaseDN, searchOptions); - - if (!Array.isArray(result) || result.length === 0) { - return false; - } - return true; - } - - extractLdapEntryData(entry) { - const values = { - _raw: entry.raw, - }; - - Object.keys(values._raw).forEach(key => { - const value = values._raw[key]; - - if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) { - if (value instanceof Buffer) { - values[key] = value.toString(); - } else { - values[key] = value; - } - } - }); - - return values; - } - - searchAllPaged(BaseDN, options, page) { - this.bindIfNecessary(); - - const processPage = ({ entries, title, end, next }) => { - log_info(title); - // Force LDAP idle to wait the record processing - this.client._updateIdle(true); - page(null, entries, { - end, - next: () => { - // Reset idle timer - this.client._updateIdle(); - next && next(); - }, - }); - }; - - this.client.search(BaseDN, options, (error, res) => { - if (error) { - log_error(error); - page(error); - return; - } - - res.on('error', error => { - log_error(error); - page(error); - return; - }); - - let entries = []; - - const internalPageSize = - options.paged && options.paged.pageSize > 0 - ? options.paged.pageSize * 2 - : 500; - - res.on('searchEntry', entry => { - entries.push(this.extractLdapEntryData(entry)); - - if (entries.length >= internalPageSize) { - processPage({ - entries, - title: 'Internal Page', - end: false, - }); - entries = []; - } - }); - - res.on('page', (result, next) => { - if (!next) { - this.client._updateIdle(true); - processPage({ - entries, - title: 'Final Page', - end: true, - }); - } else if (entries.length) { - log_info('Page'); - processPage({ - entries, - title: 'Page', - end: false, - next, - }); - entries = []; - } - }); - - res.on('end', () => { - if (entries.length) { - processPage({ - entries, - title: 'Final Page', - end: true, - }); - entries = []; - } - }); - }); - } - - searchAllAsync(BaseDN, options, callback) { - this.bindIfNecessary(); - - this.client.search(BaseDN, options, (error, res) => { - if (error) { - log_error(error); - callback(error); - return; - } - - res.on('error', error => { - log_error(error); - callback(error); - return; - }); - - const entries = []; - - res.on('searchEntry', entry => { - entries.push(this.extractLdapEntryData(entry)); - }); - - res.on('end', () => { - log_info('Search result count', entries.length); - callback(null, entries); - }); - }); - } - - authSync(dn, password) { - log_info('Authenticating', dn); - - try { - if (password === '') { - throw new Error('Password is not provided'); - } - this.bindSync(dn, password); - log_info('Authenticated', dn); - return true; - } catch (error) { - log_info('Not authenticated', dn); - log_debug('error', error); - return false; - } - } - - disconnect() { - this.connected = false; - this.domainBinded = false; - log_info('Disconecting'); - this.client.unbind(); - } -} diff --git a/.sandstorm-meteor-1.8/oidc_server.js b/.sandstorm-meteor-1.8/oidc_server.js deleted file mode 100644 index 91b0e8a4a..000000000 --- a/.sandstorm-meteor-1.8/oidc_server.js +++ /dev/null @@ -1,163 +0,0 @@ -Oidc = {}; - -OAuth.registerService('oidc', 2, null, function(query) { - var debug = process.env.DEBUG || false; - var token = getToken(query); - if (debug) console.log('XXX: register token:', token); - - var accessToken = token.access_token || token.id_token; - var expiresAt = +new Date() + 1000 * parseInt(token.expires_in, 10); - - var userinfo = getUserInfo(accessToken); - if (debug) console.log('XXX: userinfo:', userinfo); - - var serviceData = {}; - serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"]; - serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"]; - serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; - serviceData.accessToken = accessToken; - serviceData.expiresAt = expiresAt; - serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; - - if (accessToken) { - var tokenContent = getTokenContent(accessToken); - var fields = _.pick( - tokenContent, - getConfiguration().idTokenWhitelistFields, - ); - _.extend(serviceData, fields); - } - - if (token.refresh_token) serviceData.refreshToken = token.refresh_token; - if (debug) console.log('XXX: serviceData:', serviceData); - - var profile = {}; - profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; - profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; - if (debug) console.log('XXX: profile:', profile); - - return { - serviceData: serviceData, - options: { profile: profile }, - }; -}); - -var userAgent = 'Meteor'; -if (Meteor.release) { - userAgent += '/' + Meteor.release; -} - -var getToken = function(query) { - var debug = process.env.DEBUG || false; - var config = getConfiguration(); - if (config.tokenEndpoint.includes('https://')) { - var serverTokenEndpoint = config.tokenEndpoint; - } else { - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; - } - var requestPermissions = config.requestPermissions; - var response; - - try { - response = HTTP.post(serverTokenEndpoint, { - headers: { - Accept: 'application/json', - 'User-Agent': userAgent, - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - scope: requestPermissions, - state: query.state, - }, - }); - } catch (err) { - throw _.extend( - new Error( - 'Failed to get token from OIDC ' + - serverTokenEndpoint + - ': ' + - err.message, - ), - { response: err.response }, - ); - } - if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error( - 'Failed to complete handshake with OIDC ' + - serverTokenEndpoint + - ': ' + - response.data.error, - ); - } else { - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; - } -}; - -var getUserInfo = function(accessToken) { - var debug = process.env.DEBUG || false; - var config = getConfiguration(); - // Some userinfo endpoints use a different base URL than the authorization or token endpoints. - // This logic allows the end user to override the setting by providing the full URL to userinfo in their config. - if (config.userinfoEndpoint.includes('https://')) { - var serverUserinfoEndpoint = config.userinfoEndpoint; - } else { - var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; - } - var response; - try { - response = HTTP.get(serverUserinfoEndpoint, { - headers: { - 'User-Agent': userAgent, - Authorization: 'Bearer ' + accessToken, - }, - }); - } catch (err) { - throw _.extend( - new Error( - 'Failed to fetch userinfo from OIDC ' + - serverUserinfoEndpoint + - ': ' + - err.message, - ), - { response: err.response }, - ); - } - if (debug) console.log('XXX: getUserInfo response: ', response.data); - return response.data; -}; - -var getConfiguration = function() { - var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); - if (!config) { - throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); - } - return config; -}; - -var getTokenContent = function(token) { - var content = null; - if (token) { - try { - var parts = token.split('.'); - var header = JSON.parse(new Buffer(parts[0], 'base64').toString()); - content = JSON.parse(new Buffer(parts[1], 'base64').toString()); - var signature = new Buffer(parts[2], 'base64'); - var signed = parts[0] + '.' + parts[1]; - } catch (err) { - this.content = { - exp: 0, - }; - } - } - return content; -}; - -Oidc.retrieveCredential = function(credentialToken, credentialSecret) { - return OAuth.retrieveCredential(credentialToken, credentialSecret); -}; diff --git a/.sandstorm-meteor-1.8/package-lock.json b/.sandstorm-meteor-1.8/package-lock.json deleted file mode 100644 index 4431ebb29..000000000 --- a/.sandstorm-meteor-1.8/package-lock.json +++ /dev/null @@ -1,4361 +0,0 @@ -{ - "name": "wekan", - "version": "v3.90.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/runtime": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", - "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", - "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "1.13.0", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - } - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - } - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, - "backoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", - "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", - "requires": { - "precond": "0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "bcrypt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", - "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", - "requires": { - "node-addon-api": "^2.0.0", - "node-pre-gyp": "0.14.0" - } - }, - "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "bson": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.0.3.tgz", - "integrity": "sha512-7uBjjxwOSuGLmoqGI1UXWpDGc0K2WjR7dC6iaOg4iriNZo6M2EEBb8co4dEPJ5ArYCebPMie0ecgX0TWF+ZUrQ==", - "requires": { - "buffer": "^5.1.0", - "long": "^4.0.0" - } - }, - "buffer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", - "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.10.6", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, - "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - }, - "dependencies": { - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - } - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "dependencies": { - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dtrace-provider": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", - "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - } - } - }, - "eslint-config-meteor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-meteor/-/eslint-config-meteor-0.1.1.tgz", - "integrity": "sha1-rbauIL5wOFdUV5MCuqinpk5PChM=", - "dev": true - }, - "eslint-config-prettier": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", - "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-import-resolver-meteor": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-meteor/-/eslint-import-resolver-meteor-0.4.0.tgz", - "integrity": "sha1-yGhjhAghIIz4EzxczlGQnCamFWk=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "resolve": "^1.1.6" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", - "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-meteor": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-meteor/-/eslint-plugin-meteor-6.0.0.tgz", - "integrity": "sha512-2sEW3Ow1QJMLeJPHnTJbqD3ASAyRUzgU24SKTaj2NyYC4CWYl7WmEMUl99HVlDS3qigrSnSUNMix9+3vn9TmkQ==", - "dev": true, - "requires": { - "invariant": "2.2.4" - } - }, - "eslint-plugin-prettier": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", - "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", - "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz", - "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true - }, - "flushwritable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flushwritable/-/flushwritable-1.0.0.tgz", - "integrity": "sha1-PjKNj95BKtR+c44751C00pAENJg=" - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "gridfs-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/gridfs-stream/-/gridfs-stream-1.1.1.tgz", - "integrity": "sha1-PdOhAOwgIaGBKC9utGcJY2B034k=", - "requires": { - "flushwritable": "^1.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "requires": { - "symbol-observable": "^1.1.0" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "ldap-filter": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.2.2.tgz", - "integrity": "sha1-8rhCvguG2jNSeYUFsx68rlkNd9A=", - "requires": { - "assert-plus": "0.1.5" - }, - "dependencies": { - "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=" - } - } - }, - "ldapjs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz", - "integrity": "sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "^1.0.0", - "backoff": "^2.5.0", - "bunyan": "^1.8.3", - "dashdash": "^1.14.0", - "dtrace-provider": "~0.8", - "ldap-filter": "0.2.2", - "once": "^1.4.0", - "vasync": "^1.6.4", - "verror": "^1.8.1" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "lint-staged": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.8.tgz", - "integrity": "sha512-Oa9eS4DJqvQMVdywXfEor6F4vP+21fPHF8LUXgBbVWUSWBddjqsvO6Bv1LwMChmgQZZqwUvgJSHlu8HFHAPZmA==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "commander": "^4.0.1", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "execa": "^3.4.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", - "dev": true, - "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "dependencies": { - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" - }, - "dependencies": { - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - } - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", - "dev": true - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - } - } - }, - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true - }, - "loglevel-colored-level-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", - "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "loglevel": "^1.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "meteor-node-stubs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.0.0.tgz", - "integrity": "sha512-QJwyv23wyXD3uEMzk5Xr/y5ezoVlCbHvBbrgdkVadn84dmifLRbs0PtD6EeNw5NLIk+SQSfxld7IMdEsneGz5w==", - "requires": { - "assert": "^1.4.1", - "browserify-zlib": "^0.2.0", - "buffer": "^5.2.1", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.12.0", - "domain-browser": "^1.2.0", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.0", - "process": "^0.11.10", - "punycode": "^2.1.1", - "querystring-es3": "^0.2.1", - "readable-stream": "^3.3.0", - "stream-browserify": "^2.0.2", - "stream-http": "^3.0.0", - "string_decoder": "^1.2.0", - "timers-browserify": "^2.0.10", - "tty-browserify": "0.0.1", - "url": "^0.11.0", - "util": "^0.11.1", - "vm-browserify": "^1.1.0" - }, - "dependencies": { - "asn1.js": { - "version": "4.10.1", - "bundled": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "assert": { - "version": "1.4.1", - "bundled": true, - "requires": { - "util": "0.10.3" - }, - "dependencies": { - "util": { - "version": "0.10.3", - "bundled": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "base64-js": { - "version": "1.3.0", - "bundled": true - }, - "bn.js": { - "version": "4.11.8", - "bundled": true - }, - "brorand": { - "version": "1.1.0", - "bundled": true - }, - "browserify-aes": { - "version": "1.2.0", - "bundled": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "bundled": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "bundled": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "bundled": true, - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.0.4", - "bundled": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "bundled": true, - "requires": { - "pako": "~1.0.5" - } - }, - "buffer": { - "version": "5.2.1", - "bundled": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-xor": { - "version": "1.0.3", - "bundled": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "bundled": true - }, - "cipher-base": { - "version": "1.0.4", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "console-browserify": { - "version": "1.1.0", - "bundled": true, - "requires": { - "date-now": "^0.1.4" - } - }, - "constants-browserify": { - "version": "1.0.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "create-ecdh": { - "version": "4.0.3", - "bundled": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "create-hash": { - "version": "1.2.0", - "bundled": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "bundled": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "bundled": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "date-now": { - "version": "0.1.4", - "bundled": true - }, - "des.js": { - "version": "1.0.0", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "bundled": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "domain-browser": { - "version": "1.2.0", - "bundled": true - }, - "elliptic": { - "version": "6.4.1", - "bundled": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "events": { - "version": "3.0.0", - "bundled": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "bundled": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "hash-base": { - "version": "3.0.4", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "hash.js": { - "version": "1.1.7", - "bundled": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "bundled": true - } - } - }, - "hmac-drbg": { - "version": "1.0.1", - "bundled": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "https-browserify": { - "version": "1.0.0", - "bundled": true - }, - "ieee754": { - "version": "1.1.13", - "bundled": true - }, - "inherits": { - "version": "2.0.1", - "bundled": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "md5.js": { - "version": "1.3.5", - "bundled": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "bundled": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "bundled": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "bundled": true - }, - "os-browserify": { - "version": "0.3.0", - "bundled": true - }, - "pako": { - "version": "1.0.10", - "bundled": true - }, - "parse-asn1": { - "version": "5.1.4", - "bundled": true, - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "path-browserify": { - "version": "1.0.0", - "bundled": true - }, - "pbkdf2": { - "version": "3.0.17", - "bundled": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "process": { - "version": "0.11.10", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true - }, - "public-encrypt": { - "version": "4.0.3", - "bundled": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "punycode": { - "version": "2.1.1", - "bundled": true - }, - "querystring": { - "version": "0.2.0", - "bundled": true - }, - "querystring-es3": { - "version": "0.2.1", - "bundled": true - }, - "randombytes": { - "version": "2.1.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "bundled": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "3.3.0", - "bundled": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "bundled": true - } - } - }, - "ripemd160": { - "version": "2.0.2", - "bundled": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "setimmediate": { - "version": "1.0.5", - "bundled": true - }, - "sha.js": { - "version": "2.4.11", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "stream-browserify": { - "version": "2.0.2", - "bundled": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "bundled": true - } - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-http": { - "version": "3.0.0", - "bundled": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^3.0.6", - "xtend": "^4.0.0" - } - }, - "string_decoder": { - "version": "1.2.0", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "timers-browserify": { - "version": "2.0.10", - "bundled": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "tty-browserify": { - "version": "0.0.1", - "bundled": true - }, - "url": { - "version": "0.11.0", - "bundled": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "bundled": true - } - } - }, - "util": { - "version": "0.11.1", - "bundled": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "bundled": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "vm-browserify": { - "version": "1.1.0", - "bundled": true - }, - "xtend": { - "version": "4.0.1", - "bundled": true - } - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "optional": true - }, - "mongodb": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.5.tgz", - "integrity": "sha512-GCjDxR3UOltDq00Zcpzql6dQo1sVry60OXJY3TDmFc2SWFY6c8Gn1Ardidc5jDirvJrx2GC3knGOImKphbSL3A==", - "requires": { - "bl": "^2.2.0", - "bson": "^1.1.1", - "denque": "^1.4.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - }, - "dependencies": { - "bson": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", - "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "^6.0.1" - } - } - } - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-addon-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", - "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - } - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "page": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/page/-/page-1.11.5.tgz", - "integrity": "sha512-0JXUHc7Y8p1cPJQbhZSwaKO3p+bU3Rgny+OM5gJMKHWHvJKan/fsE5RUzEjRQolv9DzPOSVWfSOHz0lLxK19eA==", - "requires": { - "path-to-regexp": "~1.2.1" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz", - "integrity": "sha1-szcFwUAjTYc8hyHHuf2LVB7Tr/k=", - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "picomatch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "precond": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", - "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-eslint": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", - "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", - "dev": true, - "requires": { - "@typescript-eslint/parser": "^1.10.2", - "common-tags": "^1.4.0", - "core-js": "^3.1.4", - "dlv": "^1.1.0", - "eslint": "^5.0.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^1.7.0", - "pretty-format": "^23.0.1", - "require-relative": "^0.8.7", - "typescript": "^3.2.1", - "vue-eslint-parser": "^2.0.2" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", - "dev": true - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, - "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vasync": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz", - "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=", - "requires": { - "verror": "1.6.0" - }, - "dependencies": { - "verror": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz", - "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=", - "requires": { - "extsprintf": "1.2.0" - } - } - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vue-eslint-parser": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", - "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.2", - "esquery": "^1.0.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xss": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz", - "integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==", - "requires": { - "commander": "^2.9.0", - "cssfilter": "0.0.10" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "yaml": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.2.tgz", - "integrity": "sha512-omakb0d7FjMo3R1D2EbTKVIk6dAVLRxFXdLZMEUToeAvuqgG/YuHMuQOZ5fgk+vQ8cx+cnGKwyg+8g8PNT0xQg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.7" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", - "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } - } - } - } -} diff --git a/.sandstorm-meteor-1.8/package.json b/.sandstorm-meteor-1.8/package.json deleted file mode 100644 index 4cbbb0c86..000000000 --- a/.sandstorm-meteor-1.8/package.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "wekan", - "version": "v3.90.0", - "description": "Open-Source kanban", - "private": true, - "scripts": { - "lint": "eslint --cache --ext .js --ignore-path .eslintignore .", - "lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .", - "lint:staged": "lint-staged", - "prettify": "prettier --write '**/*.js' '**/*.jsx'", - "test": "npm run lint" - }, - "lint-staged": { - "*.js": [ - "meteor npm run prettify", - "meteor npm run lint:eslint:fix", - "git add --force" - ], - "*.jsx": [ - "meteor npm run prettify", - "meteor npm run lint:eslint:fix", - "git add --force" - ], - "*.json": [ - "prettier --write", - "git add --force" - ] - }, - "pre-commit": "lint:staged", - "eslintConfig": { - "extends": "@meteorjs/eslint-config-meteor" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wekan/wekan.git" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/wekan/wekan/issues" - }, - "homepage": "https://wekan.github.io", - "devDependencies": { - "eslint": "^6.8.0", - "eslint-config-meteor": "^0.1.1", - "eslint-config-prettier": "^6.10.0", - "eslint-import-resolver-meteor": "^0.4.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-meteor": "^6.0.0", - "eslint-plugin-prettier": "^3.1.2", - "lint-staged": "^10.0.8", - "pre-commit": "^1.2.2", - "prettier": "^1.19.1", - "prettier-eslint": "^9.0.1" - }, - "dependencies": { - "@babel/runtime": "^7.8.7", - "ajv": "^6.12.0", - "babel-runtime": "^6.26.0", - "bcrypt": "^4.0.1", - "bson": "^4.0.3", - "bunyan": "^1.8.12", - "es6-promise": "^4.2.8", - "gridfs-stream": "^1.1.1", - "ldapjs": "^1.0.2", - "meteor-node-stubs": "^1.0.0", - "mongodb": "^3.5.5", - "os": "^0.1.1", - "page": "^1.11.5", - "qs": "^6.9.1", - "source-map-support": "^0.5.16", - "xss": "^1.0.6" - } -} diff --git a/.sandstorm-meteor-1.8/wekanCreator.js b/.sandstorm-meteor-1.8/wekanCreator.js deleted file mode 100644 index ec85d93f0..000000000 --- a/.sandstorm-meteor-1.8/wekanCreator.js +++ /dev/null @@ -1,853 +0,0 @@ -const DateString = Match.Where(function(dateAsString) { - check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); -}); - -export class WekanCreator { - constructor(data) { - // we log current date, to use the same timestamp for all our actions. - // this helps to retrieve all elements performed by the same import. - this._nowDate = new Date(); - // The object creation dates, indexed by Wekan id - // (so we only parse actions once!) - this.createdAt = { - board: null, - cards: {}, - lists: {}, - swimlanes: {}, - }; - // The object creator Wekan Id, indexed by the object Wekan id - // (so we only parse actions once!) - this.createdBy = { - cards: {}, // only cards have a field for that - }; - - // Map of labels Wekan ID => Wekan ID - this.labels = {}; - // Map of swimlanes Wekan ID => Wekan ID - this.swimlanes = {}; - // Map of lists Wekan ID => Wekan ID - this.lists = {}; - // Map of cards Wekan ID => Wekan ID - this.cards = {}; - // Map of comments Wekan ID => Wekan ID - this.commentIds = {}; - // Map of attachments Wekan ID => Wekan ID - this.attachmentIds = {}; - // Map of checklists Wekan ID => Wekan ID - this.checklists = {}; - // Map of checklistItems Wekan ID => Wekan ID - this.checklistItems = {}; - // The comments, indexed by Wekan card id (to map when importing cards) - this.comments = {}; - // Map of rules Wekan ID => Wekan ID - this.rules = {}; - // the members, indexed by Wekan member id => Wekan user ID - this.members = data.membersMapping ? data.membersMapping : {}; - // Map of triggers Wekan ID => Wekan ID - this.triggers = {}; - // Map of actions Wekan ID => Wekan ID - this.actions = {}; - - // maps a wekanCardId to an array of wekanAttachments - this.attachments = {}; - } - - /** - * If dateString is provided, - * return the Date it represents. - * If not, will return the date when it was first called. - * This is useful for us, as we want all import operations to - * have the exact same date for easier later retrieval. - * - * @param {String} dateString a properly formatted Date - */ - _now(dateString) { - if (dateString) { - return new Date(dateString); - } - if (!this._nowDate) { - this._nowDate = new Date(); - } - return this._nowDate; - } - - /** - * if wekanUserId is provided and we have a mapping, - * return it. - * Otherwise return current logged user. - * @param wekanUserId - * @private - */ - _user(wekanUserId) { - if (wekanUserId && this.members[wekanUserId]) { - return this.members[wekanUserId]; - } - return Meteor.userId(); - } - - checkActivities(wekanActivities) { - check(wekanActivities, [ - Match.ObjectIncluding({ - activityType: String, - createdAt: DateString, - }), - ]); - // XXX we could perform more thorough checks based on action type - } - - checkBoard(wekanBoard) { - check( - wekanBoard, - Match.ObjectIncluding({ - archived: Boolean, - title: String, - // XXX refine control by validating 'color' against a list of - // allowed values (is it worth the maintenance?) - color: String, - permission: Match.Where(value => { - return ['private', 'public'].indexOf(value) >= 0; - }), - }), - ); - } - - checkCards(wekanCards) { - check(wekanCards, [ - Match.ObjectIncluding({ - archived: Boolean, - dateLastActivity: DateString, - labelIds: [String], - title: String, - sort: Number, - }), - ]); - } - - checkLabels(wekanLabels) { - check(wekanLabels, [ - Match.ObjectIncluding({ - // XXX refine control by validating 'color' against a list of allowed - // values (is it worth the maintenance?) - color: String, - }), - ]); - } - - checkLists(wekanLists) { - check(wekanLists, [ - Match.ObjectIncluding({ - archived: Boolean, - title: String, - }), - ]); - } - - checkSwimlanes(wekanSwimlanes) { - check(wekanSwimlanes, [ - Match.ObjectIncluding({ - archived: Boolean, - title: String, - }), - ]); - } - - checkChecklists(wekanChecklists) { - check(wekanChecklists, [ - Match.ObjectIncluding({ - cardId: String, - title: String, - }), - ]); - } - - checkChecklistItems(wekanChecklistItems) { - check(wekanChecklistItems, [ - Match.ObjectIncluding({ - cardId: String, - title: String, - }), - ]); - } - - checkRules(wekanRules) { - check(wekanRules, [ - Match.ObjectIncluding({ - triggerId: String, - actionId: String, - title: String, - }), - ]); - } - - checkTriggers(wekanTriggers) { - // XXX More check based on trigger type - check(wekanTriggers, [ - Match.ObjectIncluding({ - activityType: String, - desc: String, - }), - ]); - } - - getMembersToMap(data) { - // we will work on the list itself (an ordered array of objects) when a - // mapping is done, we add a 'wekan' field to the object representing the - // imported member - const membersToMap = data.members; - const users = data.users; - // auto-map based on username - membersToMap.forEach(importedMember => { - importedMember.id = importedMember.userId; - delete importedMember.userId; - const user = users.filter(user => { - return user._id === importedMember.id; - })[0]; - if (user.profile && user.profile.fullname) { - importedMember.fullName = user.profile.fullname; - } - importedMember.username = user.username; - const wekanUser = Users.findOne({ username: importedMember.username }); - if (wekanUser) { - importedMember.wekanId = wekanUser._id; - } - }); - return membersToMap; - } - - checkActions(wekanActions) { - // XXX More check based on action type - check(wekanActions, [ - Match.ObjectIncluding({ - actionType: String, - desc: String, - }), - ]); - } - - // You must call parseActions before calling this one. - createBoardAndLabels(boardToImport) { - const boardToCreate = { - archived: boardToImport.archived, - color: boardToImport.color, - // very old boards won't have a creation activity so no creation date - createdAt: this._now(boardToImport.createdAt), - labels: [], - members: [ - { - userId: Meteor.userId(), - wekanId: Meteor.userId(), - isActive: true, - isAdmin: true, - isNoComments: false, - isCommentOnly: false, - swimlaneId: false, - }, - ], - // Standalone Export has modifiedAt missing, adding modifiedAt to fix it - modifiedAt: this._now(boardToImport.modifiedAt), - permission: boardToImport.permission, - slug: getSlug(boardToImport.title) || 'board', - stars: 0, - title: boardToImport.title, - }; - // now add other members - if (boardToImport.members) { - boardToImport.members.forEach(wekanMember => { - // do we already have it in our list? - if ( - !boardToCreate.members.some( - member => member.wekanId === wekanMember.wekanId, - ) - ) - boardToCreate.members.push({ - ...wekanMember, - userId: wekanMember.wekanId, - }); - }); - } - boardToImport.labels.forEach(label => { - const labelToCreate = { - _id: Random.id(6), - color: label.color, - name: label.name, - }; - // We need to remember them by Wekan ID, as this is the only ref we have - // when importing cards. - this.labels[label._id] = labelToCreate._id; - boardToCreate.labels.push(labelToCreate); - }); - const boardId = Boards.direct.insert(boardToCreate); - Boards.direct.update(boardId, { - $set: { - modifiedAt: this._now(), - }, - }); - // log activity - Activities.direct.insert({ - activityType: 'importBoard', - boardId, - createdAt: this._now(), - source: { - id: boardToImport.id, - system: 'Wekan', - }, - // We attribute the import to current user, - // not the author from the original object. - userId: this._user(), - }); - return boardId; - } - - /** - * Create the Wekan cards corresponding to the supplied Wekan cards, - * as well as all linked data: activities, comments, and attachments - * @param wekanCards - * @param boardId - * @returns {Array} - */ - createCards(wekanCards, boardId) { - const result = []; - wekanCards.forEach(card => { - const cardToCreate = { - archived: card.archived, - boardId, - // very old boards won't have a creation activity so no creation date - createdAt: this._now(this.createdAt.cards[card._id]), - dateLastActivity: this._now(), - description: card.description, - listId: this.lists[card.listId], - swimlaneId: this.swimlanes[card.swimlaneId], - sort: card.sort, - title: card.title, - // we attribute the card to its creator if available - userId: this._user(this.createdBy.cards[card._id]), - isOvertime: card.isOvertime || false, - startAt: card.startAt ? this._now(card.startAt) : null, - dueAt: card.dueAt ? this._now(card.dueAt) : null, - spentTime: card.spentTime || null, - }; - // add labels - if (card.labelIds) { - cardToCreate.labelIds = card.labelIds.map(wekanId => { - return this.labels[wekanId]; - }); - } - // add members { - if (card.members) { - const wekanMembers = []; - // we can't just map, as some members may not have been mapped - card.members.forEach(sourceMemberId => { - if (this.members[sourceMemberId]) { - const wekanId = this.members[sourceMemberId]; - // we may map multiple Wekan members to the same wekan user - // in which case we risk adding the same user multiple times - if (!wekanMembers.find(wId => wId === wekanId)) { - wekanMembers.push(wekanId); - } - } - return true; - }); - if (wekanMembers.length > 0) { - cardToCreate.members = wekanMembers; - } - } - // set color - if (card.color) { - cardToCreate.color = card.color; - } - // insert card - const cardId = Cards.direct.insert(cardToCreate); - // keep track of Wekan id => Wekan id - this.cards[card._id] = cardId; - // // log activity - // Activities.direct.insert({ - // activityType: 'importCard', - // boardId, - // cardId, - // createdAt: this._now(), - // listId: cardToCreate.listId, - // source: { - // id: card._id, - // system: 'Wekan', - // }, - // // we attribute the import to current user, - // // not the author of the original card - // userId: this._user(), - // }); - // add comments - const comments = this.comments[card._id]; - if (comments) { - comments.forEach(comment => { - const commentToCreate = { - boardId, - cardId, - createdAt: this._now(comment.createdAt), - text: comment.text, - // we attribute the comment to the original author, default to current user - userId: this._user(comment.userId), - }; - // dateLastActivity will be set from activity insert, no need to - // update it ourselves - const commentId = CardComments.direct.insert(commentToCreate); - this.commentIds[comment._id] = commentId; - // Activities.direct.insert({ - // activityType: 'addComment', - // boardId: commentToCreate.boardId, - // cardId: commentToCreate.cardId, - // commentId, - // createdAt: this._now(commentToCreate.createdAt), - // // we attribute the addComment (not the import) - // // to the original author - it is needed by some UI elements. - // userId: commentToCreate.userId, - // }); - }); - } - const attachments = this.attachments[card._id]; - const wekanCoverId = card.coverId; - if (attachments) { - attachments.forEach(att => { - const file = new FS.File(); - // Simulating file.attachData on the client generates multiple errors - // - HEAD returns null, which causes exception down the line - // - the template then tries to display the url to the attachment which causes other errors - // so we make it server only, and let UI catch up once it is done, forget about latency comp. - const self = this; - if (Meteor.isServer) { - if (att.url) { - file.attachData(att.url, function(error) { - file.boardId = boardId; - file.cardId = cardId; - file.userId = self._user(att.userId); - // The field source will only be used to prevent adding - // attachments' related activities automatically - file.source = 'import'; - if (error) { - throw error; - } else { - const wekanAtt = Attachments.insert(file, () => { - // we do nothing - }); - self.attachmentIds[att._id] = wekanAtt._id; - // - if (wekanCoverId === att._id) { - Cards.direct.update(cardId, { - $set: { - coverId: wekanAtt._id, - }, - }); - } - } - }); - } else if (att.file) { - file.attachData( - new Buffer(att.file, 'base64'), - { - type: att.type, - }, - error => { - file.name(att.name); - file.boardId = boardId; - file.cardId = cardId; - file.userId = self._user(att.userId); - // The field source will only be used to prevent adding - // attachments' related activities automatically - file.source = 'import'; - if (error) { - throw error; - } else { - const wekanAtt = Attachments.insert(file, () => { - // we do nothing - }); - this.attachmentIds[att._id] = wekanAtt._id; - // - if (wekanCoverId === att._id) { - Cards.direct.update(cardId, { - $set: { - coverId: wekanAtt._id, - }, - }); - } - } - }, - ); - } - } - // todo XXX set cover - if need be - }); - } - result.push(cardId); - }); - return result; - } - - // Create labels if they do not exist and load this.labels. - createLabels(wekanLabels, board) { - wekanLabels.forEach(label => { - const color = label.color; - const name = label.name; - const existingLabel = board.getLabel(name, color); - if (existingLabel) { - this.labels[label.id] = existingLabel._id; - } else { - const idLabelCreated = board.pushLabel(name, color); - this.labels[label.id] = idLabelCreated; - } - }); - } - - createLists(wekanLists, boardId) { - wekanLists.forEach((list, listIndex) => { - const listToCreate = { - archived: list.archived, - boardId, - // We are being defensing here by providing a default date (now) if the - // creation date wasn't found on the action log. This happen on old - // Wekan boards (eg from 2013) that didn't log the 'createList' action - // we require. - createdAt: this._now(this.createdAt.lists[list.id]), - title: list.title, - sort: list.sort ? list.sort : listIndex, - }; - const listId = Lists.direct.insert(listToCreate); - Lists.direct.update(listId, { - $set: { - updatedAt: this._now(), - }, - }); - this.lists[list._id] = listId; - // // log activity - // Activities.direct.insert({ - // activityType: 'importList', - // boardId, - // createdAt: this._now(), - // listId, - // source: { - // id: list._id, - // system: 'Wekan', - // }, - // // We attribute the import to current user, - // // not the creator of the original object - // userId: this._user(), - // }); - }); - } - - createSwimlanes(wekanSwimlanes, boardId) { - wekanSwimlanes.forEach((swimlane, swimlaneIndex) => { - const swimlaneToCreate = { - archived: swimlane.archived, - boardId, - // We are being defensing here by providing a default date (now) if the - // creation date wasn't found on the action log. This happen on old - // Wekan boards (eg from 2013) that didn't log the 'createList' action - // we require. - createdAt: this._now(this.createdAt.swimlanes[swimlane._id]), - title: swimlane.title, - sort: swimlane.sort ? swimlane.sort : swimlaneIndex, - }; - // set color - if (swimlane.color) { - swimlaneToCreate.color = swimlane.color; - } - const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate); - Swimlanes.direct.update(swimlaneId, { - $set: { - updatedAt: this._now(), - }, - }); - this.swimlanes[swimlane._id] = swimlaneId; - }); - } - - createChecklists(wekanChecklists) { - const result = []; - wekanChecklists.forEach((checklist, checklistIndex) => { - // Create the checklist - const checklistToCreate = { - cardId: this.cards[checklist.cardId], - title: checklist.title, - createdAt: checklist.createdAt, - sort: checklist.sort ? checklist.sort : checklistIndex, - }; - const checklistId = Checklists.direct.insert(checklistToCreate); - this.checklists[checklist._id] = checklistId; - result.push(checklistId); - }); - return result; - } - - createTriggers(wekanTriggers, boardId) { - wekanTriggers.forEach(trigger => { - if (trigger.hasOwnProperty('labelId')) { - trigger.labelId = this.labels[trigger.labelId]; - } - if (trigger.hasOwnProperty('memberId')) { - trigger.memberId = this.members[trigger.memberId]; - } - trigger.boardId = boardId; - const oldId = trigger._id; - delete trigger._id; - this.triggers[oldId] = Triggers.direct.insert(trigger); - }); - } - - createActions(wekanActions, boardId) { - wekanActions.forEach(action => { - if (action.hasOwnProperty('labelId')) { - action.labelId = this.labels[action.labelId]; - } - if (action.hasOwnProperty('memberId')) { - action.memberId = this.members[action.memberId]; - } - action.boardId = boardId; - const oldId = action._id; - delete action._id; - this.actions[oldId] = Actions.direct.insert(action); - }); - } - - createRules(wekanRules, boardId) { - wekanRules.forEach(rule => { - // Create the rule - rule.boardId = boardId; - rule.triggerId = this.triggers[rule.triggerId]; - rule.actionId = this.actions[rule.actionId]; - delete rule._id; - Rules.direct.insert(rule); - }); - } - - createChecklistItems(wekanChecklistItems) { - wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => { - // Create the checklistItem - const checklistItemTocreate = { - title: checklistitem.title, - checklistId: this.checklists[checklistitem.checklistId], - cardId: this.cards[checklistitem.cardId], - sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex, - isFinished: checklistitem.isFinished, - }; - const checklistItemId = ChecklistItems.direct.insert( - checklistItemTocreate, - ); - this.checklistItems[checklistitem._id] = checklistItemId; - }); - } - - parseActivities(wekanBoard) { - wekanBoard.activities.forEach(activity => { - switch (activity.activityType) { - case 'addAttachment': { - // We have to be cautious, because the attachment could have been removed later. - // In that case Wekan still reports its addition, but removes its 'url' field. - // So we test for that - const wekanAttachment = wekanBoard.attachments.filter(attachment => { - return attachment._id === activity.attachmentId; - })[0]; - - if (typeof wekanAttachment !== 'undefined' && wekanAttachment) { - if (wekanAttachment.url || wekanAttachment.file) { - // we cannot actually create the Wekan attachment, because we don't yet - // have the cards to attach it to, so we store it in the instance variable. - const wekanCardId = activity.cardId; - if (!this.attachments[wekanCardId]) { - this.attachments[wekanCardId] = []; - } - this.attachments[wekanCardId].push(wekanAttachment); - } - } - break; - } - case 'addComment': { - const wekanComment = wekanBoard.comments.filter(comment => { - return comment._id === activity.commentId; - })[0]; - const id = activity.cardId; - if (!this.comments[id]) { - this.comments[id] = []; - } - this.comments[id].push(wekanComment); - break; - } - case 'createBoard': { - this.createdAt.board = activity.createdAt; - break; - } - case 'createCard': { - const cardId = activity.cardId; - this.createdAt.cards[cardId] = activity.createdAt; - this.createdBy.cards[cardId] = activity.userId; - break; - } - case 'createList': { - const listId = activity.listId; - this.createdAt.lists[listId] = activity.createdAt; - break; - } - case 'createSwimlane': { - const swimlaneId = activity.swimlaneId; - this.createdAt.swimlanes[swimlaneId] = activity.createdAt; - break; - } - } - }); - } - - importActivities(activities, boardId) { - activities.forEach(activity => { - switch (activity.activityType) { - // Board related activities - // TODO: addBoardMember, removeBoardMember - case 'createBoard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'board', - activityTypeId: boardId, - activityType: activity.activityType, - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // List related activities - // TODO: removeList, archivedList - case 'createList': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'list', - activityType: activity.activityType, - listId: this.lists[activity.listId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Card related activities - // TODO: archivedCard, restoredCard, joinMember, unjoinMember - case 'createCard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - listId: this.lists[activity.listId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - case 'moveCard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - oldListId: this.lists[activity.oldListId], - activityType: activity.activityType, - listId: this.lists[activity.listId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Comment related activities - case 'addComment': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - commentId: this.commentIds[activity.commentId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Attachment related activities - case 'addAttachment': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'card', - activityType: activity.activityType, - attachmentId: this.attachmentIds[activity.attachmentId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Checklist related activities - case 'addChecklist': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - checklistId: this.checklists[activity.checklistId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - case 'addChecklistItem': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - checklistId: this.checklists[activity.checklistId], - checklistItemId: activity.checklistItemId.replace( - activity.checklistId, - this.checklists[activity.checklistId], - ), - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - } - }); - } - - //check(board) { - check() { - //try { - // check(data, { - // membersMapping: Match.Optional(Object), - // }); - // this.checkActivities(board.activities); - // this.checkBoard(board); - // this.checkLabels(board.labels); - // this.checkLists(board.lists); - // this.checkSwimlanes(board.swimlanes); - // this.checkCards(board.cards); - //this.checkChecklists(board.checklists); - // this.checkRules(board.rules); - // this.checkActions(board.actions); - //this.checkTriggers(board.triggers); - //this.checkChecklistItems(board.checklistItems); - //} catch (e) { - // throw new Meteor.Error('error-json-schema'); - // } - } - - create(board, currentBoardId) { - // TODO : Make isSandstorm variable global - const isSandstorm = - Meteor.settings && - Meteor.settings.public && - Meteor.settings.public.sandstorm; - if (isSandstorm && currentBoardId) { - const currentBoard = Boards.findOne(currentBoardId); - currentBoard.archive(); - } - this.parseActivities(board); - const boardId = this.createBoardAndLabels(board); - this.createLists(board.lists, boardId); - this.createSwimlanes(board.swimlanes, boardId); - this.createCards(board.cards, boardId); - this.createChecklists(board.checklists); - this.createChecklistItems(board.checklistItems); - this.importActivities(board.activities, boardId); - this.createTriggers(board.triggers, boardId); - this.createActions(board.actions, boardId); - this.createRules(board.rules, boardId); - // XXX add members - return boardId; - } -} diff --git a/.travis.yml b/.travis.yml index 52da07c38..188993ac6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ -dist: eoan +dist: focal sudo: required env: TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0 - TRAVIS_NODE_VERSION: 12.15.0 + TRAVIS_NODE_VERSION: 12.22.3 TRAVIS_NPM_VERSION: latest before_install: diff --git a/.tx/config b/.tx/config index 57b3ba400..8fb7d447b 100644 --- a/.tx/config +++ b/.tx/config @@ -39,7 +39,7 @@ host = https://www.transifex.com # tap:i18n requires us to use `-` separator in the language identifiers whereas # Transifex uses a `_` separator, without an option to customize it on one side # or the other, so we need to do a Manual mapping. -lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK +lang_map = ar_EG:ar-EG, bg_BG:bg, de_CH:de-CH, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_419:es-LA, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el, fa_IR:fa-IR, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK [wekan.application] file_filter = i18n/.i18n.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb394584..c487cc890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2913 @@ +[Mac ChangeLog](https://github.com/wekan/wekan/wiki/Mac) + +Note: With Docker, please don't use latest tag. Only use release tags. +See https://github.com/wekan/wekan/issues/3874 + +# v5.38 2021-07-18 Wekan release + +This release adds the following new features: + +- [Added Code View `` button when `RICHER_CARD_COMMENT_EDITOR=true` and in desktop view + where is enough screen space for buttons (not added to mobile + view)](https://github.com/wekan/wekan/commit/ec01e5182d6b8c848d752540887a8113472b0226). + Thanks to xet7. + +and adds the following updates: + +- Updated dependencies + [Part 1](https://github.com/wekan/wekan/commit/7024929881c05cad472de74c86517cf80c8e240c), + [Part 2](https://github.com/wekan/wekan/commit/609adcdf100db226c5f310577195afa4b1a4aead). + Thanks to developers of dependencies. +- [Updated to Node.js v12.22.3](https://github.com/wekan/wekan/commit/d538a01d1962464cf4cb001462669150eeafaa99). + Thanks to Node.js developers. + +and fixes the following bugs: + +- [Fixed Line break which is wrongly added in Cards description and Cards + comments](https://github.com/wekan/wekan/commit/ec01e5182d6b8c848d752540887a8113472b0226). + Thanks to Emile840 and xet7. +- [Fixed rebuild-wekan.sh](https://github.com/wekan/wekan/commit/1d5dd5e60fec151de6c7dce7ef4e758b562923b9). + Thanks to xet7. +- [Small fixes for ModernDark theme](https://github.com/wekan/wekan/pull/3902). + Thanks to jghaanstra. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.37 2021-07-04 Wekan release + +This release adds the following CRITICAL SECURITY UPDATES: + +- [Updated to Node.js v12.22.2](https://github.com/wekan/wekan/commit/4feffd90e3f466609e09524e0ddccdafa2faef32). + Thanks to Node.js developers. + +and fixes the following bugs: + +- [Building OpenAPI docs is broken in Wekan v3.56](https://github.com/wekan/wekan/pull/3889). + Thanks to bentiss. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.36 2021-06-29 Wekan release + +This release adds the following new features: + +- [Added some controls and warning messages when user try to delete an organization or team that has + at least one user belongs to it](https://github.com/wekan/wekan/pull/3865). + Thanks to Emile840. +- Shared Templates. In Progress. + [Part 1](https://github.com/wekan/wekan/commit/0a0cec6ef0eb55391608aade897004db430ba10a). + Template Containers visible at All Boards page, with white border around board icon. + [Part 2](https://github.com/wekan/wekan/commit/d1d4453120005de61eaf2cbadc6a7b9d80e75fc1). + Ablity to Add Template Container, checkbox in Create Board popup. + Do not create Template Container by default, when creating user. + [Part 3](https://github.com/wekan/wekan/commit/7f17bc9fb03d6f4b43a2cd71ecc372e0f1b0f491). + Template container titles "Card/List/Board Templates" automatically translated. + Thanks to xet7. + [Part 4](https://github.com/wekan/wekan/commit/3b4a44abb1c1c4339c3d1b00dfac1c69ec3684cd). + Hide this Shared Templates feature while it's not finished yet. + Added back creating Template Container by default, when creating user. + Thanks to xet7. +- [Added testsuite](https://github.com/wekan/wekan/pull/3872). + Thanks to jankapunkt. +- [Delete user at REST API and `Admin Panel/People/People`](https://github.com/wekan/wekan/commit/9e16a405d8ca32a4e1be9cf89f8f978a2985593c). + There is still bug of leaving empty user avatars to boards: boards members, card members and assignees have + empty users. So it would be better to delete user from all boards before deleting user. + Thanks to darren-teo and xet7. + +and adds the following improvements: + +- [Removed unused exceljs from client bundle](https://github.com/wekan/wekan/pull/3871). + This decreased Wekan browserside frontend amount of Javascript from 5.4 MB to 4.3 MB. + Thanks to jankapunkt. +- Added note: With Docker, please don't use latest tag. Only use release tags. + See https://github.com/wekan/wekan/issues/3874 . + [Part 1](https://github.com/wekan/wekan/commit/f18a57b059994b8a6a3588a69cf095fe599b3a90), + [Part 2](https://github.com/wekan/wekan/commit/c4cea9e71b467731fd8290538dd039b7691097af). + Thanks to xet7. + +and fixes the following bugs: + +- [Fixed tests, that need to be in tests directory to not get build + errors](https://github.com/wekan/wekan/commit/56197274b6c4782fa20c7d9b5b9d58255d1f830a). + Thanks to xet7. +- Try to fix tests. + [Part 1](https://github.com/wekan/wekan/commit/78555f57a7c2ba0fb3e3986608bcf11509af9a21), + [Part 2](https://github.com/wekan/wekan/commit/7f648720afa42a2b53bfdee7e709fd891eb33373), + [Part 3](https://github.com/wekan/wekan/commit/0f34d407a43c8a63d882e69ea64ea17fc4b22c7b). + Thanks to xet7. +- [Fixed "Search All Boards" instructions are gone](https://github.com/wekan/wekan/commit/30ffcc924663f39406b250d93b14384a2f38ab6a). + Thanks to ClaudiaK21 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.35 2021-06-14 Wekan release + +This release adds the following new features: + +- Wait Spinners can now be translated + [Part 1](https://github.com/wekan/wekan/commit/8703dd42296d531450eb21a3d3adea17558a8500), + [Part 1](https://github.com/wekan/wekan/commit/7f3f0825573b1f8a7b0388e4bacbb0bd2525e886). + Added Wait Spinners docs: https://github.com/wekan/wekan/wiki/Wait-Spinners . + Thanks to xet7. +- Maximize Card. + [Part 1](https://github.com/wekan/wekan/commit/8c572502436a2eb22bd1eb1e4069c1c9145e2070), + [Part 2](https://github.com/wekan/wekan/pull/3863). + Thanks to mfilser and xet7. +- Export Card to PDF. In Progress, does not work yet. + [Part 1](https://github.com/wekan/wekan/commit/a2f2ce11354a8dbfdd6759e3b65797e4be4cc6ec), + [Part 2](https://github.com/wekan/wekan/commit/17acf1884850d8d95ae79493289adf18966df652). + Thanks to xet7. + +and removes some not needed files: + +- [Reduced Wekan bundle size from 636 MB to 467 MB by deleting all dependencies of lucasantoniassi:accounts-lockout and including + only required 10 files](https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d). + Wekan Docker image size changed from 269.6 MB to 165.1 MB. + Thanks to xet7. + +and adds the following improvements: + +- [Add border and update label colors for better visibility](https://github.com/wekan/wekan/commit/2e1eb1e224c83f16a384316626d7a4183639d4cd). + Thanks to xet7. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/f80fcfd7c0a83f4181c7a0b8beb52da9ba1446d3). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Manual sort number 0 accepted](https://github.com/wekan/wekan/pull/3861). + Thanks to mfilser. +- Allow board members to use more of API. Please add issue (or pull request) if this allows too much. + [Part 1](https://github.com/wekan/wekan/commit/a719e8fda1f78bcbf9af6e7b4341f8be1d141e90), + [Part 2](https://github.com/wekan/wekan/commit/164b6e9070199dca36d12fa3048d6b22bf6850b0). + Thanks to JayVii and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.34 2021-06-11 Wekan release + +This release adds the following new features: + +- [View and change card sort number](https://github.com/wekan/wekan/pull/3857). + Thanks to mfilser. +- [More spinners + configureable in admin panel](https://github.com/wekan/wekan/pull/3858). + Thanks to mfilser. +- [Added remaining spinner settings](https://github.com/wekan/wekan/commit/488b765f95ad67b19630cd125543836c04eaa24f). + Thanks to xet7. + +and adds the following new improvements: + +- [Card Description has now the same color on view and editing](https://github.com/wekan/wekan/pull/3851). + Thanks to mfilser. +- [Development in docker container](https://github.com/wekan/wekan/pull/3852). + Thanks to mfilser. + +and fixes the following bugs: + +- [Fix Google SSO to access Wekan has not been working by reverting Wekan v5.31 not-working fixes + to OAUTH2_LOGIN_STYLE=redirect Has No Effect](https://github.com/wekan/wekan/commit/1e837dec11dc5cb266b83efcff4f462aa02d733d). + Thanks to unpokitodxfavor and xet7. +- [CustomFields were not created after adding 1 card](https://github.com/wekan/wekan/pull/3856). + Thanks to mfilser. +- [Try to fix BUG: Database error attempting to change a account](https://github.com/wekan/wekan/commit/762391965e6ae3cd5682d5b164131500e7d92338). + Thanks to bbyszio and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.33 2021-06-10 Wekan release + +This release adds the following new features: + +- [Assigning a user to a team or an organization](https://github.com/wekan/wekan/pull/3850). + Thanks to Emile840. + +and adds the following new improvements: + +- [Custom Fields stringtemplate, autofocus the last input box](https://github.com/wekan/wekan/pull/3849). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.32 2021-06-09 Wekan release + +This release adds the following new features: + +- [Moved many button texts etc to tooltips. Added more tooltips](https://github.com/wekan/wekan/commit/6ce5ab40a7dc013247717b5107a306eb0402cd63). + Thanks to JFa-Orkis and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.31 2021-06-09 Wekan release + +This release adds the following new features: + +- [Admin Panel: Edit Organizations and Teams](https://github.com/wekan/wekan/issues/802). + Thanks to Emile840. +- [Admin Panel: Delete Organizations and Teams](https://github.com/wekan/wekan/commit/14b2c1309f0f910c1e46b5681d3612d7ff0cbf81). + Thanks to xet7. +- [Admin Panel Organizations/Teams: Show confirm text above delete button](https://github.com/wekan/wekan/commit/16379201704ea1a43ce14859633ffb1b9fae6710). + Thanks to xet7. +- [Gantt: Retain links created between tasks. Part 1: Database changes, not active in + MIT Wekan](https://github.com/wekan/wekan/commit/07a3a0b3882147effac890514b19ff84f1d76bdb). + Thanks to benjaminhrivera. + +and adds the following updates: + +- [Removed extra package](https://github.com/wekan/wekan/commit/646497c3f041e2f562d032fe28ef29169f671ac1). + Thanks to xet7. +- [Updated dependencies](https://github.com/wekan/wekan/commit/122757ca9c091e98b31d34c3abc25caa295dbdc0). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Now new boards do not have any labels added by default](https://github.com/wekan/wekan/commit/481404e8d7bad7799c2ad34d6a94eaf5e87602c2). + Thanks to tedkoch and xet7. +- [Try to fix OAUTH2_LOGIN_STYLE=redirect Has No Effect](https://github.com/wekan/wekan/commit/78324263c1c78e7e9e99f153e3158e39f564b67a). + Thanks to 1ubuntuuser and xet7. +- [Try to fix: Wekan UI fails to finish import of closed Trello boards](https://github.com/wekan/wekan/commit/007e0f1c16c935ce580093a6aec31305c75d1e45). + Thanks to berezovskyi and xet7. +- [Partial Fix: Vote and Planning Poker: Setting date and time now works for some languages that have + ascii characters in date format](https://github.com/wekan/wekan/commit/57f31d443faaa32d6c7b53d81af3be133af5f040). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.30 2021-06-03 Wekan release + +This release adds the following new features: + +- [Planning Poker / Scrum Poker](https://github.com/wekan/wekan/pull/3836), + see https://github.com/wekan/wekan/wiki/Planning-Poker . + Thanks to helioguardabaxo. + +and fixes the following bugs: + +- [Fixed Python API example: Edit card, etc](https://github.com/wekan/wekan/commit/bf62a947fbfa7d387074550288376e682fd6ad47). + Thanks to Lucky-Shi and xet7. +- [Default language is still used although this one has been modified previously](https://github.com/wekan/wekan/pull/3833). + Thanks to Emile840. +- [Moved Keyboard Shortcuts from bottom to top of Sidebar](https://github.com/wekan/wekan/commit/659a65b8b919a49ba0beef5cc53d8e61e0f794aa). + Thanks to ClaudiaK21 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.29 2021-05-29 Wekan release + +This release adds the following new features: + +- [Excel parent card name export](https://github.com/wekan/wekan/pull/3799). + Thanks to marcungeschikts and Enishowk. + +and adds the following updates: + +- Updated dependencies + [Part 1](https://github.com/wekan/wekan/commit/62150ce6c406359fba068552b4526c60faf392bb), + [Part 2](https://github.com/wekan/wekan/commit/1d9346513e4f378379b9f5192e8dad5535287f8a), + [Part 3](https://github.com/wekan/wekan/commit/6be1a330936c89fcf478efe98dd15244a98d266d). + Thanks to developers of dependencies. +- Added updated `Forgot Password` page to GitHub issue template + [Part 1](https://github.com/wekan/wekan/commit/6d0578fd5ad5f13f5ff9a285577e35fd62bba95f), + [Part 2](https://github.com/wekan/wekan/commit/ea64b17b82cd52320c0495e16385f11031dfbe3a). + Thanks to xet7. + +and fixes the following bugs: + +- [Try to fix Snap: Removed linting packages](https://github.com/wekan/wekan/commit/8911fe5c8de941808585a7d3462305d5b3d2763d). + Thanks to xet7. +- [Removed not working GitHub workflow](https://github.com/wekan/wekan/commit/5dd6466c0aa7479015c72519f36c2485b16e3341). + Thanks to xet7. +- [Fix typos](https://github.com/wekan/wekan/pull/3813). + Thanks to spasche. +- [Fix: Impersonate user can now export Excel/CSV/TSV/JSON. + Impersonate user and export Excel/CSV/TSV/JSON is now logged into database table + impersonatedUsers](https://github.com/wekan/wekan/commit/3908cd5413b775d1ee549f0a95304cf9998d3855). + Thanks to xet7. +- [Fixed Importing JSON exports fails](https://github.com/wekan/wekan/commit/bd1de94312e428e56d6cf5f343098475573cba0b). + Thanks to KeptnArgo and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.28 2021-05-07 Wekan release + +This release adds the following new features: + +- [Mermaid Diagram](https://github.com/wekan/wekan/wiki/Mermaid-Diagram). + Thanks to xuguotong and xet7. + +and adds the following updates: + +- Updated dependencies + [Part 1](https://github.com/wekan/wekan/commit/521ef8b6dad4f00662f22702331193c16b91b482), + [Part 2](https://github.com/wekan/wekan/commit/48255f6f1e4a0caf0be006196f28295d0825eb95), + [Part 3](https://github.com/wekan/wekan/commit/a550c255e6c3bd2d609a1a45a213cdae7ab4f74d). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Fix: BG color of StartDate](https://github.com/wekan/wekan/pull/3793). + Thanks to listenerri. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.27 2021-04-29 Wekan release + +This release fixes the following bugs: + +- [Fixed Non-ASCII attachment filename will crash when downloading](https://github.com/wekan/wekan/commit/c2da47773552a61d45b010a095f73d2e441f687c). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.26 2021-04-28 Wekan release + +This release adds the following new features: + +- [Feature/mini card subtask count](https://github.com/wekan/wekan/pull/3765). + Thanks to ryanMushy. + +and fixes the following bugs: + +- [Bring back Almost-Due for Start Date](https://github.com/wekan/wekan/commit/8ca1b25daf3be60c2dc64f03830dea8437bbd8ad). + Thanks to darren-teo. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.25 2021-04-27 Wekan release + +This release adds the following new features: + +- [Swimlane in Export to Excel](https://github.com/wekan/wekan/pull/3764). + Thanks to Keelan and ryanMushy. + +and adds the following updates: + +- [Updated release scripts](https://github.com/wekan/wekan/commit/9f0f6841b01b88f5559724b047d5e245617a02c8). + Thanks to xet7. + +and fixes the following bugs: + +- [Added missing PostgreSQL password to ToroDB](https://github.com/wekan/wekan/commit/995de525d96946702536f0cdcb98ef281b9df94e). + Thanks to xet7. +- [Fixed language name of Deutsch (Schweiz)](https://github.com/wekan/wekan/commit/621c701bef1d09d4ddfc93be411cfad98869f0ae). + Thanks to urmel1960. +- [Bugfix/Summernote on paste](https://github.com/wekan/wekan/pull/3761). + Thanks to ryanMushy. +- [OpenAPI: Better handle nested schemas](https://github.com/wekan/wekan/pull/3762). + Thanks to bentiss. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.24 2021-04-24 Wekan release + +This release adds the following new features: + +- [Copy Swimlane](https://github.com/wekan/wekan/pull/3753). + Thanks to jrsupplee. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/e738177e081e4a7e83fed3389f47847403551fc2). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Fix Snap: Delete extra symlink that prevented building Snap](https://github.com/wekan/wekan/commit/45124a39f34a918b251a4a36fb016639b558f119). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.23 2021-04-22 Wekan release + +This release adds the following new features: + +- [Filtering by due date](https://github.com/wekan/wekan/pull/3731). + Thanks to mcrute. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/676bf686c7a121b0da744afce5911807a6be48fe). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Fix: Trello data without labels definition](https://github.com/wekan/wekan/pull/3733). + Thanks to jrsupplee. +- [Bug fix for Due Cards](https://github.com/wekan/wekan/pull/3741). + Thanks to jrsupplee. +- [Fix: The bg color of start at button is almost-due](https://github.com/wekan/wekan/pull/3749). + Thanks to listenerri. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.22 2021-04-16 Wekan release + +This release adds the following new translations: + +- Added German (Switzerland) (de_CH) (Schwiizerdütsch). Updated translations. + [Part 1](https://github.com/wekan/wekan/commit/09506c78f3c3439db622574eb851fa0c20d3a066), + [Part 2](https://github.com/wekan/wekan/commit/dce99c00be80cceba686fd73b4b78b6c778d78a6), + [Part 3](https://github.com/wekan/wekan/commit/6ff9c5b58d25ba52b11e5429c9cfe6ed6a97000e). + Thanks to translators. + +and fixes the following bugs: + +- [Remove allowedValues from Cards.type schema](https://github.com/wekan/wekan/pull/3724). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.21 2021-04-16 Wekan release + +This release adds the following new features: + +- [Summernote Rich Text Editor](https://github.com/wekan/wekan/pull/3720): + 1) Add new button to insert a URL link. + 2) Add new popover allowing you to edit existing URL links. + 3) Enable spell check. + 4) Allow client side grammerly extension. + Thanks to ryanMushy. + +and adds the following updates: + +- [Upgraded to Meteor 2.2](https://github.com/wekan/wekan/commit/0e7c2b4b94b1c48e8839cfba635b53cdc1a797b1). + Thanks to Meteor developers. + +and fixes the following bugs: + +- [Bugfix, date format not changed to local format](https://github.com/wekan/wekan/pull/3723). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.20 2021-04-14 Wekan release + +This release fixes the following bugs: + +- [OpenAPI: rework the allowedValues to allow for imported variables](https://github.com/wekan/wekan/pull/3715). + Thanks to bentiss. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.19 2021-04-14 Wekan release + +This release adds the following new features: + +- [Custom Field "String Template"](https://github.com/wekan/wekan/pull/3701). + Thanks to tod31. +- [1) Admin reports. An option added to the admin panel that has reports an admin can run. + Right now it has two reports for attachments and broken cards. + 2) Add the creator avatar to `cardDetails` and `minicard`. Avatar is only shown if it is selected in card settings. + 3) Added a new search operator `creator`. + 4) Bug fix for multiple label predicates](https://github.com/wekan/wekan/pull/3705). + Thanks to jrsupplee. +- [Update Admin Panel Rules report icon and add missing translations](https://github.com/wekan/wekan/commit/8417fae89cc89adb4559874050ff7c56cc08eb00). + Thanks to xet7. + +and adds the following updates: + +- [Upgraded to Meteor 2.1.1 an updated dependencies](https://github.com/wekan/wekan/commit/bb8c4325c60582cdcda5d406071586f18681e737). + Thanks to developers of dependencies. +- [Updated to Node.js v12.22.1](https://github.com/wekan/wekan/commit/2201372744639ade3ba74b6ff9115988f011b9ac). + Thanks to Node.js developers. +- [Updated release scripts](https://github.com/wekan/wekan/commit/9871bf196352edcb5475e1b0ee4983e8f312e449). + Thanks to xet7. +- [Updated caniuse-lite etc dependencies](https://github.com/wekan/wekan/commit/0857a2ea91f672201ba96f2ba635165784b30fd8). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Revert stable tag, because it did break Wekan version numbers](https://github.com/wekan/wekan/commit/5ca90f4d2245910580cb0af885fac17dcec44ef0). + Thanks to xet7. +- [Updating ARM Dockerfile](https://github.com/wekan/wekan/pull/3692). + Thanks to loganballard. +- [Added latest arm64 bundle symlink](https://github.com/wekan/wekan/commit/6fe3edebb18414ebe7e69b2de3269438662b6163). + Thanks to xet7. +- [Bug fix: Rules for moving from list/swimlane](https://github.com/wekan/wekan/pull/3706). + Thanks to jrsupplee. +- [Fixed Elements are duplicated on the view "My cards". + Rewrite routine for building the My Cards hierarchical list. + Use a separate publication for retrieving My Cards. + Fixed bug with limit and skip projection](https://github.com/wekan/wekan/pull/3708). + Thanks to jrsupplee. +- [Popover needs to be destroyed anytime the details panel is closed](https://github.com/wekan/wekan/pull/3712). + Thanks to ryanMushy. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.18 2021-04-14 Wekan release + +Not released version, because some version numbers not changed in all release files. + +# v5.17 2021-04-01 Wekan release + +This release fixes the following bugs: + +- [Fix Link dialog closes card when clicking in dialog](https://github.com/wekan/wekan/commit/454d3b5bbeed6cef8ecface7e6094cabfcc4847c). + Thanks to ryanMushy. + +and adds the following updates: + +- [Added stable tag release script](https://github.com/wekan/wekan/commit/8dfb6916c5180f5d074748840d51dd04f9adb2bb). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.16 2021-04-01 Wekan release + +This release adds the following new features: + +- [Added stable tag](https://github.com/wekan/wekan/commit/c40668be3fb9c35d44698f49ab06fec5bcabbe1b). + Thanks to rynr and xet7. +- [Added back Summernote editor. Removed emoji picker](https://github.com/wekan/wekan/commit/47ecc654b825a875074dfd4826c36e2c5c55f599). + Thanks to ryanMushy and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.15 2021-03-31 Wekan release + +This release fixes the following bugs: + +- [Fixed card sort reset](https://github.com/wekan/wekan/pull/3686). + Thanks to ednamaeG. +- [Fix bug in My Cards and Global Search](https://github.com/wekan/wekan/pull/3687). + Thanks to jrsupplee. +- [Fix bug in Due Cards introduced by last bug fix](https://github.com/wekan/wekan/pull/3688). + Thanks to jrsupplee. +- [Fixed Bug: Move Swimlane to Archive does not work anymore. Fixed lint in + router.js](https://github.com/wekan/wekan/commit/0b263cf582a9649ef72efbd289927105a27583af). + Thanks to marcungeschikts and xet7. + +and adds the following updates: + +- Updated sandstorm release script and added new node update script + [Part 1](https://github.com/wekan/wekan/commit/34b6aa0858678da937eacf9a87878bbcb476fd4b), + [Part 2](https://github.com/wekan/wekan/commit/01de3f187c90af3ac94215ba7e8c7e780c98768d), + [Part 3](https://github.com/wekan/wekan/commit/1d3673e9d320926127b46383321023f149287d6d), + [Part 4](https://github.com/wekan/wekan/commit/09b9f690b162ae2797d1996e82c96ed8b8c74221), + [Part 5](https://github.com/wekan/wekan/commit/6819303047eab17b03a0c28108fd9a2cfde23d20). + Thanks to xet7. +- Updated to Node.js v12.22.0 + [Part 1](https://github.com/wekan/wekan/commit/7a9c3972642601e9d89d5e7a3816643f91448c63), + [Part 2](https://github.com/wekan/wekan/commit/c2b7525864048694c39f9cbe8f8c4cd96e36f7aa). + Thanks to Node.js developers. +- Fixed release website script + [Part 1](https://github.com/wekan/wekan/commit/b6f60c08e55c2620fe6724ae2b5da0d9dfc9ec31), + [Part 2](https://github.com/wekan/wekan/commit/91dae9795ebd1a98b8ed12c68b78ad90a6983402). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.14 2021-03-29 Wekan release + +This release adds the following new features: + +- [Clean-up Global Search, Due Cards, and My Cards. New environment variable `RESULTS_PER_PAGE` for search + results](https://github.com/wekan/wekan/pull/3676). + Thanks to jrsupplee. +- [Added environment variable `RESULTS_PER_PAGE` to all Wekan platforms](https://github.com/wekan/wekan/commit/ba05f383ca29211c5474e06c5ba6673e712afe7a). + Thanks to xet7. + +and adds the following updates: + +- [Updated release scripts](https://github.com/wekan/wekan/commit/59580e4b0f711ca55e8cb0d73803a4ff8b56352d). + Thanks to xet7. + +and fixes the following bugs: + +- [Require signed-in user for My Cards, Due Cards, and global search](https://github.com/wekan/wekan/pull/3677). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.13 2021-03-28 Wekan release + +This release fixes the following bugs: + +- [Fixed Sandstorm Wekan attachments upload](https://github.com/wekan/wekan/commit/d4a1611b86521cd5913277cfa2c86c43958eec7b). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.12 2021-03-28 Wekan release + +This release fixes the following bugs: + +- [Fix HTTP not defined](https://github.com/wekan/wekan/commit/4c609161915cc46ebfccad3d9e7ffdecdef1f85c). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.11 2021-03-28 Wekan release + +This release adds the following new features: + +- [Added emoji picker to card description edit and card comment edit. + Removed and disabled Summernote wysiwyg editor, package-lock.json + etc](https://github.com/wekan/wekan/commit/84fde1ecfc81e89ed1895cab3bcb328e4f166a87). + Thanks to xet7. + +and adds the following updates: + +- [Updated dependencies. Fixed lint](https://github.com/wekan/wekan/commit/4e1c0fdce82e3b4add8c4ffd1832752181573e88). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.10 2021-03-28 Wekan release + +This release adds the following new features: + +- [Move swimlane from one board to another](https://github.com/wekan/wekan/pull/3674). + Thanks to jrsupplee. +- [Added translatable Move Swimlane popup title](https://github.com/wekan/wekan/commit/16665bccf912c5e907739c35f7ef5a376c81740e). + Thanks to xet7. +- [REST API: Export one attachment](https://github.com/wekan/wekan/pull/3673). + Thanks to vagnernascimento. + +and adds the following updates: + +- [Updated package-lock.json](https://github.com/wekan/wekan/commit/3145ec65a3defb8ac8d97aed7e43595f661f7100). + Thanks to developers of dependencies. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.09 2021-03-26 Wekan release + +This release adds the following improvements: + +- [Replace edit icon by plus-square on new links](https://github.com/wekan/wekan/pull/3671). + Thanks to sim51. + +and fixes the following bugs: + +- [Fix openapi docs generation](https://github.com/wekan/wekan/pull/3672). + Thanks to bentiss. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.08 2021-03-26 Wekan release + +This release adds the following new features: + +- [Admin Panel/Settings/Accounts: Hide system messages of all users](https://github.com/wekan/wekan/commit/a249ffc8054189d8e3db9b4c8f082cc7ce7dcb52). + Thanks to bbyszio, r4nc0r and xet7. + +and adds the following improvements: + +- [Add Trello attached links to the card description](https://github.com/wekan/wekan/pull/3669). + Thanks to jrsupplee. +- [Added package-lock.json and updated .gitignore](https://github.com/wekan/wekan/commit/d532a3591f338cec9a3839d43d9a1e9d69f59dc2). + Thanks to xet7. + +and adds the following new translations: + +- [Added translation: español de América Latina](https://github.com/wekan/wekan/commit/ccc9efb2703efda4e199a861920b9ec88e634b59). + Thanks to translators. + +and fixes the following bugs: + +- [Fix typos in translations](https://github.com/wekan/wekan/commit/a0e1b6f918dbb252a13db05d6b9e1f832c28654f). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.07 2021-03-19 Wekan release + +This release fixes the following bugs: + +- [Fixed sort cards feature](https://github.com/wekan/wekan/pull/3662). + Thanks to ednamaeG. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.06 2021-03-18 Wekan release + +This release fixes the following bugs: + +- [Fixed Bug: Calendar & parent cards URLs used absolute URLs](https://github.com/wekan/wekan/pull/3648). + Thanks to Majed6. +- [Fixed Bug: copy to clipboard uses pathname](https://github.com/wekan/wekan/pull/3661). + Thanks to Majed6. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.05 2021-03-11 Wekan release + +This release fixes the following bugs: + +- [Change URL scheme recognition for allowing abasurl to link](https://github.com/wekan/wekan/pull/3641). + Thanks to tod31 and chrisi51. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.04 2021-03-07 Wekan release + +This release adds the following speed improvements: + +- [Speed improvement: Delete presences older than one week, and add database index to presences + serverId](https://github.com/wekan/wekan/commit/9db3c931161adfbeb6fc52d3e4cf621fb9a4955f). + Thanks to xet7. + +and adds the following new features: + +- [Added autolinking settings in Admin Panel](https://github.com/wekan/wekan/pull/3633). + Thanks to chrisi51. +- [Add custom field editing to the REST API](https://github.com/wekan/wekan/pull/3593). + Thanks to dudeofawesome. +- [Related to custom field editing, Fixed generating API docs and Wekan Custom Fields + REST API](https://github.com/wekan/wekan/commit/0bb3b670753c6ba20b0ad63f63d273036f609ee5). + Thanks to xet7. + +and adds back the following platforms: + +- [OpenPower Minicloud emergency maintenance has finished, so can now build Wekan for + ppc64le](https://github.com/wekan/wekan/commit/ac9b23f00f10b0170b8693e1e997bfb54f807adc). + Thanks to OpenPower Minicloud. + +and fixes the following bugs: + +- [Try to fix Snap: Removed fibers multi arch from Snap, because Snap build servers do not build correctly with + it](https://github.com/wekan/wekan/commit/a44ca39eb84508441f0f8bdac852745f417f12e7). + Thanks to xet7. +- [Fix search on labels server error](https://github.com/wekan/wekan/pull/3634). + Thanks to jrsupplee. +- [Fixed Bug: inconsistent use of relative/absolute URLs](https://github.com/wekan/wekan/pull/3635). + Thanks to Majed6. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.03 2021-03-03 Wekan release + +This release adds the following changes: + +- [Hide email settings from Sandstorm Wekan Admin Panel](https://github.com/wekan/wekan/commit/626f435edf75fac68448ba2e14c62acb749f9c9b). + Thanks to ocdtrekkie and xet7. + +and fixes the following bugs: + +- [Revert Removed extra imports of Meteor. Hopefully fixes email notifications and rules + on old cars not working](https://github.com/wekan/wekan/commit/e4a9dc25ecc230829afea07dbb3915b96115f7f7). + Thanks to xet7. +- [Fixed Bug: Link at board title can not be edited](https://github.com/wekan/wekan/commit/7d3917adb79be09356d32612585029392bac1e49). + Thanks to jonesrussell42, aiac, bbyszio and xet7. + +Thanks to above GitHub and Wekan vanila.io community users for their contributions and translators for their translations. + +# v5.02 2021-03-02 Wekan release + +This release adds the following improvements: + +- [Added sort to edit card REST API](https://github.com/wekan/wekan/pull/3618). + Thanks to ChrisMagnuson. +- [Add attachmentId to the Webhook data](https://github.com/wekan/wekan/pull/3620). + Thanks to n8ores. + +and fixes the following bugs: + +- [Fix SMTP port lost after upgrade. STMP settings are made only with environment variables on non-Sandstorm platforms. + Note: Sending email on Sandstorm Wekan does not work yet](https://github.com/wekan/wekan/commit/65b8220fe53349695a335bdb8b9692f82d4b3329). + Thanks to jrsupplee and xet7. +- [Removed extra imports of Meteor](https://github.com/wekan/wekan/commit/de13b8b9bafbfb186a037ae20e845846b296ac69). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.01 2021-02-26 Wekan release + +This release fixes the following bugs: + +- [Fix typo in activities code. Fixes can not edit Custom Field](https://github.com/wekan/wekan/pull/3610). + Thanks to n8ores. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v5.00 2021-02-25 Wekan release + +This release fixes the following bugs: + +- [Fixed Unable to remove old Board, reappears](https://github.com/wekan/wekan/commit/332f830cc2e44ab66ca891690b09a425b9fd7e68). + Thanks to chirrut2, uusijani, cimm, anicolaides, Philipoo0 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.99 2021-02-25 Wekan release + +This release fixes the following CRITICAL SECURITY ISSUES: + +- [Fixed SMTP password visible to Admin at Admin Panel by using browser inspect + to see behind asterisks](https://github.com/wekan/wekan/commit/71725f1b262b385162b2544f10658a0bc22f6b41). + Thanks to Georg Krause and xet7. + +and adds the following updates: + +- [Update wekan/releases/up.sh script: Can not build ppc64le version because OpenPower Minicloud + is having emergency maintenance](https://github.com/wekan/wekan/commit/a43736b5c6196c65770d8ae17af927406dce2c43). + Thanks to xet7. +- [Updated to Meteor 2.1](https://github.com/wekan/wekan/commit/f2241ba3de82e393161ee1c456e1a947c6bdb5fc). + Thanks to Meteor developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.98 2021-02-24 Wekan release + +This release adds the following CRITICAL SECURITY FIXES: + +- [Updated Node.js to v12.21.0](https://github.com/wekan/wekan/commit/fde6a6593379277d601408ec83f6f5a4347afef0). + Thanks to Node.js developers. + +and adds the following new features: + +- [Added sort feature for viewing of cards](https://github.com/wekan/wekan/pull/3586). + Thanks to ednamaeG. +- [Made sort cards feature translatable](https://github.com/wekan/wekan/commit/09a13ef75f478b0fc02ae3cdbfe918367664aa0c). + Thanks to xet7. + +and fixes the following bugs: + +- [Fix development script to escape character](https://github.com/wekan/wekan/commit/2e9ad941c0b63b384ee215548a3f31b4a635b28b). + Thanks to xet7. +- [Fix bugs with customFields in Webhooks](https://github.com/wekan/wekan/pull/3584). + Thanks to n8ores. + +and adds the following improvements: + +- [Global Search Updates](https://github.com/wekan/wekan/pull/3597). + Thanks to jrsupplee. +- [Updated GitHub issue template links](https://github.com/wekan/wekan/commit/c23aca78babd51857271134aed9247615b87b895). + Thanks to atlantsecurity and xet7. +- [Admin Panel/People/People/New User: Added Initials](https://github.com/wekan/wekan/commit/3a2deb00399eb213472ef169826bd15ad655e490). + Thanks to xet7. + +and adds the following updates: + +- [Update release-bundle.sh script: Can not build ppc64le version because OpenPower Minicloud + is having emergency maintenance](https://github.com/wekan/wekan/commit/799ae886c5fedad3bafa18a14f8fbbca7ad2c227). + Thanks to xet7. +- [Update release.sh script: Also build Sandstorm version of Wekan with same + script](https://github.com/wekan/wekan/commit/b105088c2858bc04120551a8a8e5a75f187041e5). + Thanks to xet7. + +and adds the following new translations: + +- [Added translation: Persian (Iran)](https://github.com/wekan/wekan/commit/0a728f805b336588741ca93f2ecbd1ca31ee53f2). + Thanks to translators. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.97 2021-02-24 Wekan release + +Release skipped, because not all required files had new version number updated yet. + +# v4.96 2021-02-13 Wekan release + +This release adds the following new features: + +- [Add /api/boards_count endpoint for statistics](https://github.com/wekan/wekan/pull/3556). + Thanks to pichouk. +- [Added possibility to specify hours in single digits in 24 hour format](https://github.com/wekan/wekan/pull/3557). + Thanks to lindhork. +- [Added replacement from comma to dot](https://github.com/wekan/wekan/pull/3564). + Thanks to lindhork. + +and adds the following improvements: + +- [Checklistitems are now inserted always at the end of the checklist](https://github.com/wekan/wekan/pull/3551). + Thanks to mfilser. +- [Teams/Organizations: Added more code to Admin Panel for saving and editing. In Progress, does not work yet](https://github.com/wekan/wekan/commit/1bc07b1b4a3e8cd1a177f3f1776ed8e189bc627a). + Thanks to xet7. +- [Mobile View, list header is now always at top and only lists/cards view have a scroll area](https://github.com/wekan/wekan/pull/3563). + Thanks to mfilser. +- [Added ChangeLog update script](https://github.com/wekan/wekan/commit/c7ec07ed4748fe9b00f622af7472fd291cf1a3ce). + Thanks to xet7. +- [Helm: Made SecretEnv a secret and added default mongodb name as Wekan](https://github.com/wekan/wekan/pull/3570). + Thanks to meerkampdvv. +- [Checklist drag handle now at the left side (same place as for the checklist items)](https://github.com/wekan/wekan/pull/3571). + Thanks to mfilser. +- [Lists, show also 0 cards at column description](https://github.com/wekan/wekan/pull/3572). + Thanks to mfilser. + +and adds the following updates: + +- [Updated Node.js to v12.20.2](https://github.com/wekan/wekan/commit/011f86f368a83c2e70f597c11ec60ec857e0fab0). + Thanks to Node.js developers. + +and fixes the following bugs: + +- [Minicard, remove red line below member avatar icon](https://github.com/wekan/wekan/pull/3560). + Thanks to mfilser. +- [Added padding](https://github.com/wekan/wekan/pull/3559). + Thanks to lindhork. +- [Changed default behaviour for BIGEVENTS that no activity matches it](https://github.com/wekan/wekan/pull/3561). + Thanks to bronger. +- [Modern theme: Remove font color when the card has a color](https://github.com/wekan/wekan/pull/3569). + Thanks to helioguardabaxo. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.95 2021-02-08 Wekan release + +This release adds back the following features: + +- [Added back Custom Fields sorting, because it now does not prevent loading boards](https://github.com/wekan/wekan/pull/3547). + Thanks to mfilser. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/1c494803b091d987e26ccb783432434e7fee15a5). + Thanks to xet7. +- [Fix typo](https://github.com/wekan/wekan/commit/0bd0a70564d3dda67706deb1bbfbd1d5a96f811f). + Thanks to xet7. +- Updated release scripts + [Part 1](https://github.com/wekan/wekan/commit/d0df3a2915d08b255d7ab92f9bcac195a1e7f442), + [Part 2](https://github.com/wekan/wekan/commit/e34a2840366351c0e069515ac2210db3911dbc0f). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.94 2021-02-08 Wekan release + +This release adds the following new features: + +- [Settings, "Show cards count" now works at mobile view too](https://github.com/wekan/wekan/pull/3545). + Thanks to mfilser. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/b4352ada27545fadc425ca7024532aede3cc1a6f). + Thanks to developers of dependencies. +- [Update release scripts](https://github.com/wekan/wekan/commit/dcec5b5cb05ac9e0dfae8f360def169f5f9b6fa2). + Thanks to xet7. + +and fixes the following bugs: + +- [Fix bug in adding new users](https://github.com/wekan/wekan/pull/3544). + Thanks to jrsupplee. +- [Fixed Board does not load, by disabling Custom Fields sorting](https://github.com/wekan/wekan/commit/d57eb6a2fc73c7b25c957ad42b5f7a06f680e1a1). + Thanks to marcungeschikts, olivierlambert and xet7. +- [Fixed lint](https://github.com/wekan/wekan/commit/60fedad3fe384a2b0652941e57ecaa5fc4b7897a). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# 4.93 2021-02-06 Wekan release + +This release adds the following new features: + +- [Add the ability to call get_user operation with username](https://github.com/wekan/wekan/pull/3530). + Thanks to magicbelette. + +and adds the following updates: + +- [Updated Ubuntu base image in Dockerfile](https://github.com/wekan/wekan/commit/bcdaf77a9c675530cfa21d038e8abd7c62aef70d). + Thanks to Ubuntu and xet7. + +and fixes the following bugs: + +- [Set the language on `TAPi18n` when user selects language](https://github.com/wekan/wekan/pull/3525). + Thanks to jrsupplee. +- [Fix bug in `uniqueTitle`](https://github.com/wekan/wekan/pull/3526). + Thanks to jrsupplee. +- [Fixed file permissions in build scripts](https://github.com/wekan/wekan/commit/ea697f2238842893953dee76bed03ffd5b4a107e). + Thanks to xet7. +- [Red line below the avatar now correctly on FireFox](https://github.com/wekan/wekan/pull/3532). + Thanks to mfilser. +- [Notifications, enable line wrapping](https://github.com/wekan/wekan/pull/3533). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.92 2021-02-03 Wekan release + +This release adds the following improvements: + +- [Import user mapping improvements](https://github.com/wekan/wekan/pull/3510). + Thanks to jrsupplee. +- Added Createtoken API. + [Part 1](https://github.com/wekan/wekan/pull/3520), + [Part 2](https://github.com/wekan/wekan/commit/3774060d32abcfee17dc1c31958d4673794d8619). + Thanks to magicbelette and xet7. +- Sorted archives. + [Part 1](https://github.com/wekan/wekan/pull/3518), + [Part 2](https://github.com/wekan/wekan/commit/3da66a0fe30c2b54b63e5d098232b375d899925e). + Thanks to bronger, jrsupplee and xet7. + +and adds the following updates: + +- [Updated dependencies](https://github.com/wekan/wekan/commit/05ebae7329ba0dd3fe9b04cd63b1f983830cdeee). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Don't reload page when returning default sort](https://github.com/wekan/wekan/pull/3509). + Thanks to jrsupplee. +- [Avatar overlaped notifications](https://github.com/wekan/wekan/pull/3516). + Thanks to mfilser. +- [Hopeful fix for i18n not working in `onRendered()`](https://github.com/wekan/wekan/pull/3519). + Thanks to jrsupplee. +- [Disable some console.log code, that is only needed while developing](https://github.com/wekan/wekan/commit/f40c9804f848fdb91229c5718ad97495337109ba). + Thanks to xet7. +- [Try fix removed nonexistent document error](https://github.com/wekan/wekan/commit/f274b3c26be60813829dcf2b0e68a8dd876ff614). + Thanks to Brulf, TheMasterFX and xet7. +- [Fixed Cards and CustomFields sorted alphabetically](https://github.com/wekan/wekan/pull/3521). + Thanks to mfilser. +- [Notifications avatar overlaped at mobile view](https://github.com/wekan/wekan/pull/3523). + Thanks to mfilser. + +and improves some security related info: + +- [Added badge for CII Best Practices](https://github.com/wekan/wekan/commit/ee2f7c077fe56d6fedb8b75ae3cba6bab56f9363). + Thanks to CII and xet7. +- [Added PGP public key for sending security vulnerability reports](https://github.com/wekan/wekan/commit/a385d6f4fd76e2bb0f374963848513b9373d6b5a). + Thanks to xet7. +- [Updated security report email address](https://github.com/wekan/wekan/commit/7031b7a3c77acc0ddeabe436572dd4057001e9f5). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.91 2021-01-29 Wekan release + +This release fixes the following bugs: + +- [Use `new RegExp(...)` to define a regex](https://github.com/wekan/wekan/pull/3505). + Thanks to jrsupplee. +- [Fixed typos in docker-compose.yml](https://github.com/wekan/wekan/commit/4acf8526509ffe852e6e48e3503560448239d8cd). + Thanks to farfoodyou and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.90 2021-01-28 Wekan release + +This release adds the following new features: + +- [Create unique board names when importing](https://github.com/wekan/wekan/pull/3499). + Thanks to jrsupplee. + +and fixes the following bugs: + +- [Added missing backtick quotes](https://github.com/wekan/wekan/commit/bf7b1789ec16e3c52397318c799ec5a0fc2de3a5). + Thanks to xet7. +- [Fix some bugs when importing Wekan JSON](https://github.com/wekan/wekan/pull/3500). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.89 2021-01-28 Wekan release + +This release fixes the following bugs: + +- [Try to fix quotes in Global Search](https://github.com/wekan/wekan/commit/0ff215f78f03c81d153dfc0ffa08fac94b542ec2). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.88 2021-01-28 Wekan release + +This release adds the following new features: + +- [Additional URL schemes: SolidWorks PDM (conisio:) and abas ERP (abasurl:)](https://github.com/wekan/wekan/pull/3487). + Thanks to tod31. + +and adds the following improvements: + +- [Mobile and Desktop have now the same Quick Access view + scrollable](https://github.com/wekan/wekan/pull/3491). + Thanks to mfilser. +- [Global Search Update](https://github.com/wekan/wekan/pull/3492). + Thanks to jrsupplee. +- [Added many more fields to Export to Excel, and better formatting. Does not yet have all + fields](https://github.com/wekan/wekan/commit/37372466ccd15c7d5d4a55510b349fac0953c425). + Thanks to xet7. + +and fixes the following bugs: + +- [Changed method to create initials same as others for new user of oidc](https://github.com/wekan/wekan/pull/3489). + Thanks to sato-64bit. +- [Removed quotes from docker-compose.yml settings](https://github.com/wekan/wekan/commit/b1cdcda8ed78d48505a8da5180d7aed46a24fd64). + Thanks to XL-Reaper, Vinc89 and xet7. +- [Repair LDAP_REJECT_UNAUTHORIZED=false CVE-2021-3309](https://github.com/wekan/wekan/pull/3497). + Thanks to robert-scheck. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.87 2021-01-26 Wekan release + +This release fixes the following bugs: + +- [Reject by default LDAP connections not authorized via CA trust store](https://github.com/wekan/wekan/pull/3483). + Thanks to robert-scheck. +- [Handle '\n' line breaks in PEM-encoded SSL/TLS certificates](https://github.com/wekan/wekan/pull/3485). + Thanks to robert-scheck. + +and adds the following improvements + +- [Try parallel build of releases, does it work](https://github.com/wekan/wekan/commit/be238ac7439ce38b4403d9a611dec9bb421a856f). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.86 2021-01-25 Wekan release + +This release adds the following improvements: + +- [Added PWA related category, orientation, screenshots, maskable icon and + IARC rating ID](https://github.com/wekan/wekan/commit/027771b3021a709d9049015e7d7e6faccf1ad7f3). + Thanks to xet7. +- [Added PWA related monochrome icon](https://github.com/wekan/wekan/commit/2977f7cf47626b429159cb7b7496919c07ece914). + Thanks to xet7. +- [Mention Wekan GitHub Discussions at readme](https://github.com/wekan/wekan/commit/4c0bd359f921ae0ea722f78946fcc1168e8b939e). + Thanks to xet7. + +and adds the following updates: + +- [Use Node 12.20.1 in rebuild-wekan.sh](https://github.com/wekan/wekan/commit/37d76e9e061d31c11fca8e704e9b4c54f17c0023). + Thanks to xet7. + +and fixes the following bugs: + +- [Move call to URL search to onRendered](https://github.com/wekan/wekan/pull/3478). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.85 2021-01-23 Wekan release + +This release adds the following new features: + +- [Option to add custom field to all cards](https://github.com/wekan/wekan/pulls/3466). + Thanks to jrsupplee. +- [Added checkbox as an option to custom field create dialog](https://github.com/wekan/wekan/pull/3472). + Thanks to jrsupplee. + +and adds the following improvements: + +- [Display My Cards lists using inline-grid](https://github.com/wekan/wekan/pull/3474). + Thanks to jrsupplee. +- [Added board title link and background color to My Cards](https://github.com/wekan/wekan/pull/3471). + Thanks to helioguardabaxo. +- [Use simple border at My Cards](https://github.com/wekan/wekan/pull/3475). + Thanks to helioguardabaxo. + +and adds the following updates: + +- Updated dependencies. + [Part 1](https://github.com/wekan/wekan/commit/7a66cb46a0ec334f4e95a73322641ba029f770ad), + [Part 2](https://github.com/wekan/wekan/commit/953cfd6ecd291196ce2ad1d4a5eac19ca21a20d9). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [WIP Limit: Limited number of cards highlighting to true overbooking](https://github.com/wekan/wekan/pull/3468). + Thanks to bronger. +- [Revert table-cell back to inline-block at my-cards-list-wrapper](https://github.com/wekan/wekan/commit/da12c84609674bdf5121ad6b74c97c65b9fc0164). + Thanks to jrsupplee and xet7. +- [Fix for search operators with uppercase letters](https://github.com/wekan/wekan/pull/3470). + Thanks to jrsupplee. +- [Boards.copyTitle - escape string used in regex](https://github.com/wekan/wekan/pull/3473). + Thanks to jrsupplee. +- [Bug fix: import regex escape function](https://github.com/wekan/wekan/pull/3476). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.84 2021-01-22 Wekan release + +This release adds the following new features: + +- [Search All Boards: Added list of board, list and color names. Added operators for due, created and modified. + Added support for clicking list titles, label names and board titles. Make some heading translatable. + Set focus back to search phrase input after clicking a predicate. Fixed some spacing issues](https://github.com/wekan/wekan/pull/3459). + Thanks to jrsupplee. + +and fixes the following bugs: + +- [Fixed Upper/lowercase errors in some languages due to .toLowerCase](https://github.com/wekan/wekan/commit/a5f6dd6399142b3b05e9b6a0d106d931106807d6). + Thanks to bronger and xet7. +- [Tried to fix possible prototype pollution reported by Deepcode.ai](https://github.com/wekan/wekan/commit/8f553497e4f21d44e78243d22d80b18f729a3d6a). + Thanks to Deepcode.ai and xet7. +- [Disable some logs that are not needed anymore](https://github.com/wekan/wekan/commit/0373da44b38f7300f69470fed3cabab9b63c8783). + Thanks to xet7. +- [Rules not copied during board copy](https://github.com/wekan/wekan/pull/3458). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.83 2021-01-20 Wekan release + +This release adds the following new features: + +- [When copying a board, copy Custom Fields to new board](https://github.com/wekan/wekan/pull/3451). + Thanks to jrsupplee. + +and adds the following updates: + +- [Upgrade to Meteor 2.0](https://github.com/wekan/wekan/commit/23c1723ae1ee09101d5ad6334eee782763d0b354). + Thanks to Meteor developers. + +and fixes the following bugs: + +- [Custom field definitions duplicated on copy and move](https://github.com/wekan/wekan/pull/3449). + Thanks to jrsupplee. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.82 2021-01-20 Wekan release + +This release adds the following new features: + +- Export to Excel XLSX. Does work, but does not export all fields yet correctly. In Progress. + [Part 1](https://github.com/wekan/wekan/commit/855151a8d18c14ec26a1ef09977b0f98f4c32759). + [Part 2](https://github.com/wekan/wekan/commit/56a530058b219696146ab8f8df17b3745b538d0e). + Thanks to gameendman, alfredgu and xet7. +- [Trello Import Custom Fields. Adds a new custom field type of checkbox to make importing Trello checkbox fields easier](https://github.com/wekan/wekan/pull/3444). + Thanks to jrsupplee and xet7. + +and adds the following features back after fixing: + +- [Cards, custom fields are displayed in alphabetic order](https://github.com/wekan/wekan/pull/3442). + This was added in Wekan v4.71, removed in Wekan v4.81 and added back at Wekan v4.82. + Thanks to mfilser. + +and adds the following new translations: + +- [Translations: Added ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo)](https://github.com/wekan/wekan/commit/fc68354e836fa8f03e72d5af33b6c28e1c52f10b). + Thanks to translators and xet7. + +and fixes the following bugs: + +- [Display custom date fields in a shortened form on minicard](https://github.com/wekan/wekan/pull/3446). + Thanks to jrsupplee and xet7. +- [Fixed Card activity shows only 20 last entries of activities and comments, by changing limit to 500 entries](https://github.com/wekan/wekan/commit/8e4eade00252353be5cfda1de768fea1bb87095e). + Thanks to xet7. +- [Fixed LDAP Group Filters not working in docker](https://github.com/wekan/wekan-ldap/issues/86). + Thanks to Sancretor. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.81 2021-01-18 Wekan release + +This release adds the following new features: + +- [Global Search: Use translated color names](https://github.com/wekan/wekan/pull/3440). + Thanks to jrsupplee and xet7. + +and fixes the following bugs: + +- [Restore original working Dockerfile](https://github.com/wekan/wekan/commit/c4ea7457dcf1db200c87784c35b7d3c390e94d80). + Thanks to gpalyu and xet7. +- [Reverted Cards, custom fields are displayed in alphabetic order from Wekan v4.71 + https://github.com/wekan/wekan/pulls/3417 because it caused board not + loading](https://github.com/wekan/wekan/commit/413f91d0c8f2d3f9df9bf036bb20551dba29bc2e). + Thanks to olivierlambert and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.80 2021-01-18 Wekan release + +This release adds the following improvements: + +- [Global Search fixes and updates](https://github.com/wekan/wekan/pulls/3437). + Thanks to jrsupplee. +- [Use table-cell instead of inline-block in my-cards-list-wrapper CSS](https://github.com/wekan/wekan/commit/3866ed31965eb5b722e88c4d3e7628d516375088). + Thanks to johappel and xet7. +- [Use multi stage build based on Node images](https://github.com/wekan/wekan/pull/3438). + Thanks to GavinLilly. +- [Try to use buster base images, because when using Wekan Alpine, registering new user of Wekan does not work, + maybe because of glibc/musl](https://github.com/wekan/wekan/commit/254a9abad2dec620d95c02ac9209e9f569407986). + Thanks to GavinLilly and xet7. +- [Use MongoDB setFeatureCompatibilityVersion 4.2 on Snap. TODO: Docker](https://github.com/wekan/wekan/commit/2791b7da22ddb0ff5588eca56f1dc90ff5ffdd2d). + Thanks to GuidoDr and xet7. + +and adds the following updates: + +- [Update dependencies](https://github.com/wekan/wekan/commit/c0f748bcb5dfebe7fa90be647a1ed23f0edcc304). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Fixed Linked card makes board not load when CustomField definition is undefined](https://github.com/wekan/wekan/commit/0d5f33299ee25e1bee4ca4fc3b3c2483c29e367c). + Thanks to olivierlambert and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.79 2021-01-17 Wekan release + +This release adds the following new features: + +- [At Search All Cards, now it's possible to click found card to open it](https://github.com/wekan/wekan/commit/10f74f5152117358e9c6b9bb0e81b8c284841aff). + Thanks to xet7. + +and fixes the following bugs: + +- [Fixed: Linked card makes board not load](https://github.com/wekan/wekan/commit/be03d2ae9aa708119992548145cbaf82e1f87419). + Thanks to akitzing, galletl, pdonias, olivierlambert and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.78 2021-01-16 Wekan release + +This release adds the following new features: + +- [Search All Boards](https://github.com/wekan/wekan/pull/3433). Currently limited to 50 results. Will be improved later. + Thanks to jrsuplee and xet7. + +and fixes the following bugs: + +- [HTML is not needed in Rules translations, so disabled it](https://github.com/wekan/wekan/commit/a2894bf0cb11161f2f9382e900ffbae2a1570d38). + Thanks to jrsuplee and xet7. +- [Limit amount of data in publications where possible](https://github.com/wekan/wekan/commit/4115d62bac882ceaaec531b1f9df2666097be51a). + Thanks to xet7. +- [Fixed Display issues with assignee on minicard](https://github.com/wekan/wekan/commit/aa34da61fe80a2ab2a87b6413b3b9c25fb8ea96f). + Thanks to bronger and xet7. +- [Limit visibility of Global Search, My Cards and Due Cards to logged in users, because they do not work without + logging in](https://github.com/wekan/wekan/commit/4180224fd9841a3e6cab9eacb1447978482e1e91). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.77 2021-01-13 Wekan release + +This release adds the following new features: + +- [Show membertype (admin, normal etc) in avatar/initials tooltip for board members](https://github.com/wekan/wekan/commit/afd5d1d0c0a14702a2ea6960a58b78153975dc0d). + Thanks to bronger and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.76 2021-01-13 Wekan release + +This release adds the following new features: + +- [Try to allow links to onenote, mailspring and file](https://github.com/wekan/wekan/commit/3977f2187aa4dc0bd9ddfcd02065437df0f1a5c0). + Thanks to lime918, rgalonso, ocdtrekkie, gkarachuk and xet7. + +and adds the following improvements: + +- [Removed wekan- from export filenames for whitelabeling](https://github.com/wekan/wekan/commit/de27be0911053195838d6d0d4f1b6eae8a1d773a). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.75 2021-01-11 Wekan release + +This release adds the following CRITICAL SECURITY FIXES: + +- [Due Cards and Broken Cards: In All Users view, fixed to show cards only from other users Public Boards. Not anymore from private + boards](https://github.com/wekan/wekan/commit/801d0aacf00eace05ec70d6f0229f2a752f119cd). + Thanks to xet7. + +and adds the following updates: + +- [Upgrade to Meteor 1.12.1](https://github.com/wekan/wekan/commit/3105548c98091773e86e4556c2980d5f533e98f1). + Thanks to Meteor developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.74 2021-01-10 Wekan release + +This release adds the following improvements: + +- [My Cards and Due Cards: Added popup title and horizontal line between menu + options](https://github.com/wekan/wekan/commit/9293de541b3fcccee52751808f0e95b04986c1bb). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.73 2021-01-10 Wekan release + +This release adds the following new features: + +- [Broken Cards](https://github.com/wekan/wekan/pull/3426) for debugging. + Thanks to jrsupplee. +- ["Broken Cards" to be translatable](https://github.com/wekan/wekan/commit/d09e448fbd37ae84419aa3909b9a4594cd7ddb92). + Thanks to xet7. +- [Added Broken Cards to User Settings menu](https://github.com/wekan/wekan/commit/b98df8ef87fc0c501d4f06e3e5af292605bd21cf). + Thanks to xet7. + +and adds the following improvements: + +- [My Cards: Make code forgiving of possible null values for a card's board, swimlane, or list. + Added a new Due Cards page that displays cards with a Due date for either just the user or all + users](https://github.com/wekan/wekan/pull/3425). + Thanks to jrsupplee. + +and fixes the following bugs: + +- [Fixed Spanish language names to lowercase español](https://github.com/wekan/wekan/commit/9ec1904119e6bec4c00600cb8ea81c28e631ae2e). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.72 2021-01-09 Wekan release + +This release fixes the following bugs: + +- [Fixed badges at readme](https://github.com/wekan/wekan/pull/3421). + Thanks to kuchengrab. +- [Changed Sandstorm menus to be more similar like other Wekan versions, make Export visible, etc](https://github.com/wekan/wekan/commit/103d03d4c86df445b9d28d506f7d3098ab56368b). + Thanks to PizzaProgram and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.71 2021-01-08 Wekan release + +This release adds the following new features: + +- [My Cards add Due Date sort](https://github.com/wekan/wekan/pull/3419). + Thanks to jrsupplee. + +and adds the following improvements: + +- [Update to My Cards](https://github.com/wekan/wekan/pulls/3416). + Thanks to jrsupplee. +- [Cards, custom fields are displayed in alphabetic order](https://github.com/wekan/wekan/pulls/3417). + Thanks to mfilser. + +and fixes the following bugs: + +- [Fixed Color picker of lists is empty. Fixed error about existing file at Wekan Docker version](https://github.com/wekan/wekan/issues/3418). + Thanks to bronger and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.70 2021-01-04 Wekan release + +This release adds the following CRITICAL SECURITY FIXES: + +- [Upgrade to Node.js 12.20.1](https://github.com/wekan/wekan/commit/4bfe017b08f573991fd1f9229ae53573798f475e). + Thanks to Node developers. + +and adds the following new features: + +- [Added many new translations to Wekan, now there is total 60 translations in Wekan. Updated translations. Organized pull-translations.sh alphabetically by + language name](https://github.com/wekan/wekan/commit/d171f4088f40512d321969df3f0c280a620c0c5f). + Thanks to translators and xet7. +- [Added markdown and emoji to My Cards board, swimlane and list names](https://github.com/wekan/wekan/commit/763dc9c8e0122990c5f496392f2cce980c535dce). + Thanks to xet7. +- [Show Admin Panel / People and Version also on mobile MiniScreen](https://github.com/wekan/wekan/commit/754a91dbdc3d7111c367cb5dd0a02250a837e42a). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.69 2021-01-02 Wekan release + +This release adds the following new features: + +- Teams/Organizations to Admin Panel. In Progress. + [Part 2](https://github.com/wekan/wekan/commit/ad482d5cfb72591f1b5c749c3c0156000dbf660a). + [Part 3](https://github.com/wekan/wekan/commit/b64cd358ed0af4395357423ad172b8dac9dc3178). + Thanks to xet7. +- [My Cards](https://github.com/wekan/wekan/pull/3413). + Thanks to jrsupplee. + +and adds the following UI changes: + +- [Moved Public/Archive/Templates/etc options to click right top username Member Settings menu, where My Cards also + is](https://github.com/wekan/wekan/commit/0592b0c56ac372c87dea17f0a090e7d7569430d1). + Thanks to xet7. +- [Reorder My Cards to be first at menu](https://github.com/wekan/wekan/commit/bfc16fc5442e8cc8c3cc03df992d5b1d1724338b). + Thanks to xet7. + +and fixes the following bugs: + +- [New Checklistitems are now autoresized too](https://github.com/wekan/wekan/pull/3411). + Thanks to mfilser. +- [Swimlane + and = Icons resized for better handling at mobile view](https://github.com/wekan/wekan/pull/3412). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.68 2020-12-29 Wekan release + +This release fixes the following bugs: + +- [Checklist-Items, Drag-Drop Handle now at the left side](https://github.com/wekan/wekan/pull/3407). + Thanks to mfilser. +- [Checklist-Items, Autoresize the textarea vertically to fit the user-input](https://github.com/wekan/wekan/pull/3408). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.67 2020-12-29 Wekan release + +This release adds the following new features: + +- Teams/Organizations to Admin Panel. In Progress. + [Part 1](https://github.com/wekan/wekan/commit/9e2093d6aed38e66fc4d63823315c9382e013a32). + Thanks to xet7. + +and fixes the following bugs: + +- [Checklist Mini-Screen, appendTo: 'parent' not necessary anymore](https://github.com/wekan/wekan/pull/3405). + Thanks to mfilser. +- [Allow to edit email verified and initials at Admin Panel/People/People](https://github.com/wekan/wekan/commit/d03e2170dd10741bd78722cc35b52cffa220a2e7). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.66 2020-12-27 Wekan release + +This release fixes the following bugs: + +- [Fix Mobile miniscreen: Drag handle not visible in long checklist item + text](https://github.com/wekan/wekan/commit/a8453657c95a4bde2ae86b4c77e55bb2174adf26). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.65 2020-12-26 Wekan release + +This release fixes the following bugs: + +- [Fixed Drag and drop between checklists closes the card sometimes on + Firefox](https://github.com/wekan/wekan/commit/c7808c5c03f98eae709e5ef89e8e17af4689cb2e). + xet7 thanks mfilser about [similar fix of appendTo parent](https://github.com/wekan/wekan/pull/3342) + that did work here too to fix this. + Thanks to mfilser and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.64 2020-12-24 Wekan release + +This release fixes the following bugs: + +- [Dark theme button background fix](https://github.com/wekan/wekan/pull/3401). + Thanks to jasontamez. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.63 2020-12-21 Wekan release + +This release fixes the following bugs: + +- [Fixed Remove Cover button gives JS error](https://github.com/wekan/wekan/commit/28850e5510f2aaefcae404efac1973c12f1cca65). + Thanks to tsukasa1989 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.62 2020-12-18 Wekan release + +This release fixes the following bugs: + +- [Treat unknown attachment types as binary on board + import/clone](https://github.com/wekan/wekan/pull/3395). + Thanks to daniel-eder. +- [Fix Move card from a board to another does not work anymore](https://github.com/wekan/wekan/commit/9dd0fb88d6cb3378a8fc96aaf60214020efeaed1). + Thanks to lezioul and xet7. +- [Add some permission code, to see does it fix something](https://github.com/wekan/wekan/commit/7f3c4acf62deefa2f7b36b986e06336fd3b2754f). + Thanks to xet7. +- [Fix delete board button not visible](https://github.com/wekan/wekan/commit/53a925cf7ff95167cbf2f65f7c7e169e18b14b44). + Thanks to airtraxx and xet7. +- [Board: When removing member from board, remove also from assignees. + Admin Panel/People: 1) Allow edit user that does not have email address. + 2) When creating new user, require username and email address, and save also fullname. + 3) Some in progress code for deleting user, that does not work correctly yet, so deleting user is not enabled + yet](https://github.com/wekan/wekan/commit/61ae62a83aaddb2c6f679ce9d05b675c845ba8bf). + Thanks to airtraxx and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.61 2020-12-16 Wekan release + +This release fixes the following bugs: + +- [Removed cookie code that is not in use](https://github.com/wekan/wekan/commit/3c406d955ec602d4b86c164acb9e94e715086f8e). + Thanks to xet7. +- [Allow normal user to delete checklist item](https://github.com/wekan/wekan/commit/f9ba17177e8f1146be1e571f47d26c13a9337034). + Thanks to Samunosuke and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.60 2020-12-15 Wekan release + +This release adds the following updates: + +- Update dependencies. + [Part 1](https://github.com/wekan/wekan/commit/2e1e703e3591258462b3167f72e2b2319bf57bec), + [Part 2](https://github.com/wekan/wekan/commit/a75c162483795775db1631686f8e7017b42c87ca). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- Prevent normal user deleting or modifying too much. Allow normal user to export board. + [Part1](https://github.com/wekan/wekan/commit/4a205fcfcb40438faead3bf8973b10b8e42974f0), + [Part2](https://github.com/wekan/wekan/commit/6cb4b9fe4a086202d642de54464088e0a1122ec0). + Thanks to Samunosuke, pgh2357 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.59 2020-12-10 Wekan release + +This release fixes the following bugs: + +- [Fix not all checklist items being imported/cloned](https://github.com/wekan/wekan/pull/3389). + Thanks to daniel-eder. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.58 2020-12-09 Wekan release + +This release fixes the following bugs: + +- [Fix issues when duplicating board](https://github.com/wekan/wekan/pull/3387). + Thanks to daniel-eder. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.57 2020-12-08 Wekan release + +This release adds the following new features: + +- [Helm: Introduce secretEnv for secret value provisioning](https://github.com/wekan/wekan/pull/3382). + Thanks to ThoreKr. +- REST API: List attachments of a board, with download URLs of attachments. In Progress. + [Part 1](https://github.com/wekan/wekan/commit/bf94161f30adf9dec6aa41af6946ba54c1573a44), + [Part 2](https://github.com/wekan/wekan/commit/2ec53b27d14049bc9622861492cac301512a1e33), + [Part 3](https://github.com/wekan/wekan/commit/36e29a405ee943d15e6e1bd9ac02ecefb7a7a06f). + For using this, Python code example: + https://github.com/wekan/wekan/wiki/New-card-with-Python3-and-REST-API . + Thanks to xet7. +- REST API: Added Wekan REST API Python CLI, for adding card, etc. In Progress, + downloading attachments does not work yet. + [Part1](https://github.com/wekan/wekan/commit/051f7b2769c51404063e7f0ddf85fbd0f9508a88), + [Part2](https://github.com/wekan/wekan/commit/387f0600ce1389aab955cc125d331dcd5eeeafdd). + Thanks to xet7. +- [Drag handles at checklist items on mobile view](https://github.com/wekan/wekan/pull/3342). + Thanks to mfilser. + +and adds the following updates: + +- [Upgrade to Meteor 1.12](https://github.com/wekan/wekan/commit/7a6abaac44b1235021dc7cc25e3224515c64a068). + Thanks to Meteor developers. +- [Upgrade to Node 12.20.0](https://github.com/wekan/wekan/commit/015f4d671d136a4a344fe82e2a6bcbe0c2be6cfd). + Thanks to Node developers. + +and fixes the following bugs: + +- [Fixed Quay Docker builds that failed](https://github.com/wekan/wekan/issues/3380) + because of Docker Hub rate limits by copying base images from Docker Hub to Quay. + [Part1](https://github.com/wekan/wekan/commit/4537971300c6ffcc85b7dd930867eb942bd22f86), + [Part2](https://github.com/wekan/wekan/commit/2b2884d996b8fc6101eff50db058639631eb5945), + [Part3](https://github.com/wekan/wekan/commit/c09758fb913d73e9229f43d17663b3c4715a62b9). + Thanks to xet7. +- [Hide duplicate create board button, because it did not show board templates + correctly](https://github.com/wekan/wekan/commit/a7977a8fc7f171d046a228f81fb0cd481b0ccc41). + Thanks to xet7. +- [Minicard, reduce space after assignees label](https://github.com/wekan/wekan/pull/3385). + Thanks to mfilser. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.56 2020-11-30 Wekan release + +This release adds the following new features: + +- [Added date notification icons](https://github.com/wekan/wekan/pull/3366). + This partially resolves [#3363](https://github.com/wekan/wekan/issues/3363). + Thanks to helioguardabaxo. +- Attempt to implement date activities notification [Part1](https://github.com/wekan/wekan/pull/3372) + and [Part2](https://github.com/wekan/wekan/pull/3373). + Thanks to helioguardabaxo. +- [Sticky swimlane](https://github.com/wekan/wekan/pull/3370). + Thanks to progressify and xet7. +- [1) New default: sudo snap set wekan mongo-log-destination='devnull'. + Other options: syslog/snapcommon. + This should lower amount of disk usage and logs. + 2) Tried to fix command: + `sudo snap set wekan mongo-url='...'`](https://github.com/wekan/wekan/commit/5510c2a37dc6bcfa0ec588eceb8dc9f32cec9851). + Thanks to xet7. + +and adds the following improvements: + +- [Improvements in activities design](https://github.com/wekan/wekan/pull/3374). + Thanks to helioguardabaxo. + +and fixes the following bugs: + +- [Fix typo on MONGO_URL on Snap](https://github.com/wekan/wekan/commit/05a72f7c627e05ac4ce38cb9588f2aac45273ce8). + Thanks to xet7. +- [Fix: Update helm mongodb dependency](https://github.com/wekan/wekan/pull/3369). + Thanks to jiangytcn. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.55 2020-11-21 Wekan release + +This release adds the following improvements: + +- [Set minimum height to icons at All Boards page](https://github.com/wekan/wekan/commit/6193a0b64e85df2f2353192e7efb16680b436622). + Thanks to xet7. +- [Increase avatar size](https://github.com/wekan/wekan/pull/3360). + Thanks to centralhardware. +- [Modern Dark theme: card details as lightbox](https://github.com/wekan/wekan/pull/3359). + Thanks to jghaanstra. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.54 2020-11-17 Wekan release + +This release adds the following new features: + +- [Add keyboard shortcut / for search](https://github.com/wekan/wekan/pull/3354). + Thanks to helioguardabaxo. +- [Added back variable auto height of icons at All Boards page](https://github.com/wekan/wekan/commit/9dbb10c59b4bb1b16b7942cc0b60741a1fbe9110). + Thanks to xet7. + +and adds the following updates: + +- [Upgrade to Node.js 12.19.1](https://github.com/wekan/wekan/commit/f786afc4b9d0e5e4a057b5f3d7995377873022cf). + Thanks to Node.js developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.53 2020-11-15 Wekan release + +This release adds the following updates: + +- [Use Quay for Docker builds, because Docker Hub builds show ENOMEM error](https://github.com/wekan/wekan/commit/22501749da708a51b89ef9ff0aee4ac6ba529ed9). + Thanks to xet7. +- [Update Docker upgrade info](https://github.com/wekan/wekan/commit/d0040754ea75a26926a187ce47dbe529a15e0926). + Thanks to xet7. +- [Use latest MongoDB on Docker](https://github.com/wekan/wekan/commit/8250cbcf6e417d21ffdc4f14495792768b0bc9ef). + Thanks to xet7. +- [In rebuild-wekan.sh install dependencies, Install npm](https://github.com/wekan/wekan/commit/345e2357c8fd030e943f9729f790db980d3a727c). + Thanks to xet7. +- [In rebuild-wekan.sh install dependencies, uncomment chown](https://github.com/wekan/wekan/commit/21aebe845f3f4911d9bb824f0f33bdb19a3a9af6). + Thanks to xet7. +- [Update markdown-it and markdown-it-emoji dependencies](https://github.com/wekan/wekan/commit/222fca3ad7aa2b67329dca64e84eb72899fd8137). + Thanks to developers of markdown-it and markdown-it-emoji. + +and adds the following improvements: + +- [Minor improvements to Modern Dark theme](https://github.com/wekan/wekan/pull/3348). + Thanks to jghaanstra. +- [Added missing bottom padding to lists](https://github.com/wekan/wekan/pull/3351). + Thanks to danger89. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.52 2020-11-12 Wekan release + +This release adds the following improvements: + +- [Some more small improvements to Modern Dark theme](https://github.com/wekan/wekan/pull/3346). + Thanks to jghaanstra. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.51 2020-11-11 Wekan release + +This release adds the following new features: + +- [Board admin can edit and delete comments as well](https://github.com/wekan/wekan/pull/3340). + Thanks to mfilser. +- [Drag handles for checklist](https://github.com/wekan/wekan/pull/3341). + Thanks to mfilser. +- [Custom URL Schemes autolinked](https://github.com/wekan/wekan/pull/3339). + Thanks to brian-j. + +and adds the following improvements: + +- [Improvements to Modern Dark Theme](https://github.com/wekan/wekan/pull/3344). + Thanks to jghaanstra. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.50 2020-11-10 Wekan release + +This release adds the following new features: + +- Add 'Modern Dark' theme [Part1](https://github.com/wekan/wekan/pulls/3335) + and [Part2](https://github.com/wekan/wekan/commit/6801c960b115be4265bf18ba05c444ac79aef887). + Thanks to jghaanstra, helioguardabaxo and xet7. + +and fixes the following bugs: + +- [Fix edit description to require only one click](https://github.com/wekan/wekan/commit/0ef248574c2751be1245c5748a9cbbe5ba2969b5). + Thanks to uusijani and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.49 2020-11-04 Wekan release + +This release adds the following new features: + +- [LDAP: Sync email address](https://github.com/wekan/wekan/pull/3329). + Thanks to gramakri. + +and adds the following changes: + +- [Changed board icons bigger at All Boards page](https://github.com/wekan/wekan/commit/76273300e749ebe3b1d711dee84336d03b31ed49). + Thanks to xet7. + +and adds the following translations: + +- [Translate some part of Gantt chart of Wekan Gantt GPL version](https://github.com/wekan/wekan/commit/fd363c69cc6e1cf3a283e3dbcc323edb1eae896e). + This only adds translations to all Wekan versions, not any GPL code to MIT version. + Thanks to xet7. + +and fixes the following bugs: + +- [Fix Trello import](https://github.com/wekan/wekan/commit/faad739f974a0392ca73e4db03e5267edcc5dec7). + Thanks to elct9620 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.48 2020-11-02 Wekan release + +This release adds the following new features: + +- [Smaller board icons to All Boards Page, and use full page width, so more board icons fit visible at once. + Removed variable height, because different heights made it look a little unbalanced](https://github.com/wekan/wekan/commit/0a5f9307d27a4b77aa7ff005701fea8ce0d50ec8). + Thanks to xet7. +- [Admin Panel / Settings / Layout / Custom Top Left Corner Logo Height](https://github.com/wekan/wekan/commit/4cfddf1d8d37bdbbb58c050333ee6ea2afc3e6f9). + Thanks to xet7. +- [When RICHER_CARD_COMMENT_EDITOR=true, use richer editor also when editing card description](https://github.com/wekan/wekan/commit/4e2d337620ac490b8e99ee968e6f92477e09b900). + Thanks to xet7. + +and removes the following dependencies: + +- [Removed hot-module-replacement and mdg:meteor-apm-agent](https://github.com/wekan/wekan/commit/aa454a5542e5ab1d581eef50cdb5c96ac2ada940). + Thanks to xet7. + +and fixes the following bugs: + +- [Fix Clone Board](https://github.com/wekan/wekan/commit/f4fdb94a3fcd63432ef7ded4df970b3491700020). + Thanks to e-gaulue and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.47 2020-11-01 Wekan release + +This release fixes the following bugs: + +- [Fix: OAuth2 fails with self-signed server certificate](https://github.com/wekan/wekan/pull/3325) and + [Added related settings for OAUTH2_CA_CERT that is optional OAuth2 CA Cert](https://github.com/wekan/wekan/commit/55252300c601ea40dc8adad1887397b31ceb0bb2). + Thanks to faust64 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.46 2020-10-30 Wekan release + +This release adds the following updates: + +- [Upgrade to Meteor 2.0-beta.4](https://github.com/wekan/wekan/commit/af583145ed2b36af8e6c72765fd35d70a292fad6). + Thanks to Meteor developers. + +and fixes the following bugs: + +- [Fix: Use current boardId when a worker moves a card](https://github.com/wekan/wekan/pull/3323). + Thanks to jtbairdsr. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.45 2020-10-30 Wekan release + +This release fixes the following bugs: + +- [Fix can not upload and download files, by changing back to Node.js 12.19.0 and adding + fast-render](https://github.com/wekan/wekan/commit/d2f434879caa20d69651f23fa2124074f55c9893). + Current file storing to MongoDB code was not yet compatible with newer Node.js. + Thanks to eskogito and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.44 2020-10-28 Wekan release + +This release adds the following new features: + +- For development, [add Meteor 2.0 Hot Module Replacement](https://github.com/wekan/wekan/commit/e6162472548d9dff497dd76e82d23044f779f757). + More info at https://forums.meteor.com/t/meteor-2-0-beta-with-hot-module-replacement-hmr/54313/8 . + Thanks to zodern. + +and adds the following updates: + +- [Upgrade to Node.js 14.15.0](https://github.com/wekan/wekan/commit/045e9db7b8f0de852ef4486cb1ad200d6ca7296d). + Thanks to Node.js and Meteor.js developers. +- [Upgrade to Meteor 2.0-beta.3. Removed fast-render and ostrio:cookies](https://github.com/wekan/wekan/commit/a463f2a855498935db5b66e5fad446ce465adab1). + Thanks to Meteor.js developers and xet7. + +and fixes the following bugs: + +- [Fixed: With ORACLE_OIM_ENABLED, allow setting OAUTH2_REQUEST_PERMISSIONS with environment variable](https://github.com/wekan/wekan/commit/1b429b3f99c32840ebb0ff9a29015aa8c28ec644). + Thanks to xet7. +- [Changed public board changing Swimlanes/Lists/Calendar view and changing Hide minicard label text + from using cookies to using browser localStorage](https://github.com/wekan/wekan/commit/460b1d3a664b648bc03c40422b9d175401e229c1), + to remove some errors from browser inspect console. + Thanks to xet7. +- [Fix Modern theme board canvas background](https://github.com/wekan/wekan/pull/3312). + Thanks to helioguardabaxo. +- [Fix: 1) Expose moving cards on mobile to workers. 2) Hide the move to another board functionality + in the submenu (only from the worker) so that the worker is still constrained to a single + board](https://github.com/wekan/wekan/pull/3315). + Thanks to jtbairdsr. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.43 2020-10-20 Wekan release + +This release adds the following new features: + +- [Allow more than one assignee](https://github.com/wekan/wekan/commit/acf9e7caeaf59e1030ae1014c0cb2fb7dae27147). + Thanks to xet7. + +and fixes the following bugs: + +- [Fixed CSV/TSV export](https://github.com/wekan/wekan/commit/d7333dec84328ca191f430d96aaf9e550840631a). + Please test and report any problems [at issue #3173](https://github.com/wekan/wekan/issues/3173). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.42 2020-10-14 Wekan release + +This release adds the following updates: + +- [Upgrade to Node.js 12.19.0](https://github.com/wekan/wekan/commit/b8a209249e968b90917af319adf24fedf2157396). + Thanks to Node.js developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.41 2020-10-03 Wekan release + +This release adds the following new features: + +- [Login with OIDC OAuth2 Oracle on premise identity manager OIM, with setting ORACLE_OIM_ENABLED=true](https://github.com/wekan/wekan/commit/ec8a78537f1dc40e967de36a02ea09cf7398318a). + More info [at wiki](https://github.com/wekan/wekan/wiki/Oracle-OIM). + Thanks to xet7. +- [At Admin Panel / Layout: Text below Custom Login Logo. Can have markdown formatting](https://github.com/wekan/wekan/commit/7223d6e75057d1412862a97b8a43c34ec23b16e9). + Thanks to xet7. + +and adds the following updates: + +- [Update dependencies](https://github.com/wekan/wekan/commit/b796a6cbf4911c14ff036a51db0252e08d3a5ef8). + Thanks to developers of dependencies. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.40 2020-09-18 Wekan release + +This release adds the following new features: + +- Custom Logo for Login and Top Left Corner. Optional link when clicking logo. + Settings at Admin Panel / Layout. + [Part 1](https://github.com/wekan/wekan/commit/a7c3317ed696fad8e211b22afbb3012f3a4f2ddb), + [Part 2](https://github.com/wekan/wekan/commit/05e3fc31b4633978a6b002a0325aad8e74d57ec4), + [Part 3](https://github.com/wekan/wekan/commit/3fc80e1145b23f8e6c7492ef4e3313b02f3d8772). + Thanks to xet7. + +and adds the following updates: + +- [Upgrade to Meteor v1.11.1](https://github.com/wekan/wekan/commit/185cf163b23280af5a7910381209984e2362a452). + Thanks to Meteor developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.39 2020-09-17 Wekan release + +This release adds the following new features: + +- [Impersonate one user](https://github.com/wekan/wekan/pull/3280) and + [related translatable strings](https://github.com/wekan/wekan/commit/81ac0fdba9b52477dbbe7b6ed01b6d299288bcca). + Thanks to Akuket and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.38 2020-09-16 Wekan release + +This release fixes the following CRITICAL VULNERABILITIES: + +- [Upgrade to Node.js v12.18.4](https://github.com/wekan/wekan/commit/5cd9f89b21e6f800c2b78da49a1c0cf7f6fba955). + Thanks to Node.js developers. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.37 2020-09-15 Wekan release + +This release adds the following UI improvements: + +- [UI improvements in filter, multi-selection and rules](https://github.com/wekan/wekan/pull/3279). + Thanks to helioguardabaxo and xet7. + +and adds the following updates: + +- [Update release scripts](https://github.com/wekan/wekan/commit/e79b7fad0a35f29020c48a4a4eedb435573c9bf1). + Thanks to xet7. + +and fixes the following bugs: + +- [Fix parse error in docker-compose.yml](https://github.com/wekan/wekan/pull/3278). + Thanks to devilmengcry. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.36 2020-09-15 Wekan release + +This release adds the following new features: + +- [Added translations for date selection popups](https://github.com/wekan/wekan/commit/f9b0da65f1de48a2af11aa7afbd767559ba95b79). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.35 2020-09-14 Wekan release + +This release tries to fix the following bugs: + +- [Try to fix Snap](https://github.com/wekan/wekan/commit/7173e293ef6b0d3c1fe82b5320340589c72c9326). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.34 2020-09-14 Wekan release + +This release tries to fix the following bugs: + +- [Try to fix Snap](https://github.com/wekan/wekan/commit/d2b84c7773f20b34bca8be23078469a8809005a6). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.33 2020-09-14 Wekan release + +This release adds the following login settings: + +- [Added some CAS and SAML settings](https://github.com/wekan/wekan/commit/214c86cc22f4c721a79ec0a4a4f3bbd90d673f93). + Not tested. Please test and send pull requests if it does not work. + See https://github.com/wekan/wekan/wiki/SAML and https://github.com/wekan/wekan/wiki/CAS . + Thanks to xet7. + +and updates some dependencies: + +- [Update dependencies](https://github.com/wekan/wekan/commit/cca041e21a66087ca4008a22cb0f5b4176801101). + Thanks to developers of dependencies. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.32 2020-09-13 Wekan release + +This release tried to fix the following bugs: + +- [Try to fix Snap](https://github.com/wekan/wekan/commit/). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.31 2020-09-13 Wekan release + +This release tried to fix the following bugs: + +- [Try to fix Snap](https://github.com/wekan/wekan/commit/fe62e12ab46c41ea30ba79795b0dc39b3451d4a2). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.30 2020-09-13 Wekan release + +This release adds the following new features and improvements: + +- [Add setting for OAUTH2_ADFS_ENABLED=true](https://github.com/wekan/wekan/pull/3269) + for [SSO Integration with ADFS 4.0 using OAuth 2 and OpenID](https://github.com/wekan/wekan/issues/3184). + Thanks to phaseshift3r. +- [Add setting OAUTH2_ADFS_ENABLED=false for most platforms. Remove mouse scroll settings of already removed custom scrollbar. + Add testing for both string and boolean version of true](https://github.com/wekan/wekan/commit/f6bdb4d694453d73f4bfa6a75814833594cf5000). + Thanks to xet7. +- [Design improvements in templates, card details and custom fields](https://github.com/wekan/wekan/pull/3271) + and [related change to translation](https://github.com/wekan/wekan/commit/fe40c5fd37a7c54240c080caf98b6130229f5d31). + Thanks to helioguardabaxo and xet7. + +and adds the following updates: + +- [Use forked & updated version of gridfs-stream](https://github.com/wekan/wekan/pull/3270). + Thanks to blaggacao. +- [Update dependencies](https://github.com/wekan/wekan/pull/3268). + Thanks to blaggacao. +- [Update npm-mongo dependency](https://github.com/wekan/wekan/commit/9fdafd20081b20302af3d1a6397fb840348f1209). + Thanks to filipenevola. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.29 2020-09-11 Wekan release + +This release adds the following new features: + +- [Changed markdown from marked to markdown-it](https://github.com/wekan/wekan/commit/20b01771055ca4d8871d13abb559ab92ecee10f4) and + added emoji support https://github.com/wekan/wekan/wiki/Emoji . + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.28 2020-09-11 Wekan release + +This release updates some dependencies: + +- [Update some dependencies](https://github.com/wekan/wekan/commit/125c4684bd6815a8f49241bc2663e82112afe67b). + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Fix card scrollbar on Windows](https://github.com/wekan/wekan/pull/3264). + Thanks to tborychowski. +- [Try to fix language names](https://github.com/wekan/wekan/commit/f81fd8084fd6cd1ad57daefcc22ed1fb0acaaeca). + Thanks to buplet, xoas and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.27 2020-09-09 Wekan release + +This release fixes the following bugs: + +- [Reverted incomplete fix for "Checklist + card title with starting number and point", because it disabled some markdown. + Also more fixes to GFM checklist not displayed properly](https://github.com/wekan/wekan/commit/bf18792d7733d6e6cfb61a8d6db4caafdcc19b34). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.26 2020-09-05 Wekan release + +This release adds the following quality checks: + +- [Added GitHub automatic code quality analysis](https://github.com/wekan/wekan/commit/df35683043603f6ecb9bd4f2a4b357e374397ad1). + Thanks to xet7. + +and updates the following dependencies: + +- [bl](https://github.com/wekan/wekan/commit/7ec671bb9f8a33c5eb28c26b98143f9b4cd9b958). + Thanks to developers of dependencies. +- [Delete markdown demo that is not in use](https://github.com/wekan/wekan/commit/d344c39d497cc291ee7927fdda900dc8bac22bc2). + Thanks to xet7. +- [Update markdown and xss](https://github.com/wekan/wekan/commit/cfcbf640d64bdfc4f3a482c32e35f396e1a22191) + Thanks to developers of dependencies. + +and fixes the following bugs: + +- [Disable list formatting and converting to HTML. This fixes markdown numbering and viewing bugs](https://github.com/wekan/wekan/commit/41b1c55988a9a65005ac0b9e1ddcc0596c047a49). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.25 2020-08-31 Wekan release + +This release adds the following docker-compose.yml changes: + +- [Mongo 4.4 does not work. Mongo 4.2 and 3.x works](https://github.com/wekan/wekan/commit/5d2daa4a80c819f0610ff2f17589de1e1085836c). + Thanks to GuidoDr for info. Related https://github.com/wekan/wekan/issues/3247 + +and adds the following Nextcloud documentation: + +- [Improving documentation for Nextcloud integration](https://github.com/wekan/wekan/pull/3248). + Thanks to relikd. + +and removes the following code and allows double quotes in code: + +- Removed custom scrollbar [1](https://github.com/wekan/wekan/pull/3246) and [2](https://github.com/wekan/wekan/commit/5870d38e8e63159ede8c18d1766a4f9f6ba8987c). + Also in eslint settings allowed double quotes in code. Thanks to tborychowski and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.24 2020-08-27 Wekan release + +This release adds the following updates: + +- [Upgrade to Meteor 1.11](https://github.com/wekan/wekan/commit/4d49265b25595444553e1c2d6e48c7a699949654). + Thanks to Meteor developers. +- [Update bcrypt](https://github.com/wekan/wekan/commit/dee7020a5aaa90c8580ef42fa73aff0ca4ae3e12). + Thanks to bcrypt developers. +- [Update dependencies](https://github.com/wekan/wekan/commit/60b2787c559b9966d6040a622c5b971fa95241c3). + Thanks to developers of dependencies. + +and adds the following translations: + +- [Add Trigger and Action header words to Rule Details](https://github.com/wekan/wekan/pull/3244). + Thanks to helioguardabaxo. +- [Add Spanish (Peru) (es_PE)](https://github.com/wekan/wekan/commit/b9f87bf310b4f071c8219bb7511b15a7fa27340d). + Thanks to translators. + +and adds the following mouse scroll settings: + +- [Add setting for mouse scroll deltafactor. Fix snap setting for mouse scroll amount](https://github.com/wekan/wekan/commit/7e4b791c2964f4b130abbaee62ffdff1536450c4). + Thanks to danger89 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.23 2020-08-13 Wekan release + +This release fixes following CRITICAL VULNERABILITIES: + +- [Update vulnerable dependency elliptic that is dependency of meteor-node-stubs that is dependency of + Wekan](https://github.com/wekan/wekan/commit/910f0cecbe7a4b3fdff603e5e74c2cb1c40b660b). + Thanks to filipenevola, neeldug, L25inux and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.22 2020-07-23 Wekan release + +This release adds back these features: + +- [Export to JSON and HTML .zip file](https://github.com/commit/1624fc82f7c319e84a78f29445c7867f7da15c32) + that also fixes #3216 Clone Boards not working. + Thanks to xet7. + +and hides these features temporarily: + +- [Hide CSV export until it's fixed in EdgeHTML compatible way](https://github.com/wekan/wekan/commit/045b8a84a29dde09201dd5108c757719d00e6f55). + Thanks to xet7. + +and adds the following updates: + +- [Upgrade to Node 12.18.3](https://github.com/wekan/wekan/commit/6f503ca818abff17a20b6612aeea1f9e2c4a8234). + Thanks to Node developers. + +and fixes the following bugs: + +- [Fix Snap](https://github.com/wekan/wekan/commit/68391a943bd37d9f98819ffb7b7a29692d0bd380). + This fix was already included to Wekan v4.21 to get it released. + TODO: Sometime migrate from Caddy v1 to Caddy v2. + Thanks to xet7. +- [Fix detecting current IP address on rebuild-wekan.sh](https://github.com/wekan/wekan/commit/ec1d8f275ff4cd720a8cd3bc918b32f9c5f5d099). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.21 2020-07-21 Wekan release + +This release adds the following new features: + +- [REST API: Changed edit_card and get_card_by_customfields to return full documents](https://github.com/wekan/wekan/pull/3215). + Thanks to gvespignani70. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.20 2020-07-20 Wekan release + +This release adds the following updates: + +- Update dependencies [Part1](https://github.com/wekan/wekan/commit/419615bed43b6e9de4030193c47137a066b85bde) and + [Part2](https://github.com/wekan/wekan/commit/116372e11e09ce9b8376a8694553add595e02815). + Thanks to developers of dependencies and xet7. + +and fixes the following bugs: + +- [Change slug on card rename](https://github.com/wekan/wekan/pull/3214). + Thanks to NicoP-S. +- [Add missing Wekan logo sizes for PWAs and Apps](https://github.com/wekan/wekan/commit/de28bf8569a7373a5d6fd60a4f413e76673adc26). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.19 2020-07-18 Wekan release + +This release adds the following features: + +- [Add support for EdgeHTML browser (Microsoft Legacy Edge, not based on Chromium) by removing incompatible csv-stringify package. + CSV export will be fixed later](https://github.com/wekan/wekan/commit/b9a4b0b51d3692fcbb715b1afc875f21cd204ae5). + Thanks to xet7. + +and adds the following updates: + +- Update dependencies [Part1](https://github.com/wekan/wekan/commit/23ee93ca3d4ea161a93627a8e28e1ce93eea1bab), + [Part2](https://github.com/wekan/wekan/commit/6646d48ccbaf04c4935de35fe037eff3bd7fd469), + [Part3](https://github.com/wekan/wekan/commit/87cb4598f745a362aaac06b8b457198c40aaf61e), + [Part4](https://github.com/wekan/wekan/commit/f57ed2990f5c6e1af10d270b24c7092805711afe). + Thanks to developers of dependencies and xet7. + +and fixes the following bugs: + +- [Checklist Item PUT API: boolean cast on isFinished](https://github.com/wekan/wekan/pull/3211). + Thanks to Robert-Lebedeu. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.18 2020-07-10 Wekan release + +This release adds the following updates: + +- [Upgrade to Node 12.18.2](https://github.com/wekan/wekan/commit/6e4407ed9cb3c95a99e0dbbb4383324dd57d6be1). + Thanks to Node developers and xet7. +- [Update dependencies](https://github.com/wekan/wekan/commit/05cd1247ab935f586d747743bb9cd79d23e0b1e6). + Thanks to dependency developers and xet7. + +and fixes the following bugs: + +- [All logged in users are now allowed to reorder boards by dragging at All Boards page and Public Boards page](https://github.com/wekan/wekan/commit/ba24c4e40c728d030504ed21ccf79247d0449e1b). + Thanks to xet7. +- [Fix running meteor for dev in rebuild-wekan.sh](https://github.com/wekan/wekan/commit/a77cf56fbdaf0b74d8b97aa41b0a88fee85e3ee1). + Thanks to xet7. +- [Fix start-wekan.bat](https://github.com/wekan/wekan/commit/0be1c00fccef8797a1b3612593a6623a9b465e0d) and + [Windows bundle install](https://github.com/wekan/wekan/wiki/Windows#a-bundle-with-windows-nodemongodb). + Thanks to xet7. +- [Fix typo](https://github.com/wekan/wekan/pull/3197). + Thanks to Lua00808. +- [Fix creating user misbehaving in some special case](https://github.com/wekan/wekan/pull/3206). + Thanks to salleman33. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.17 2020-06-18 Wekan release + +This release fixes the following bugs: + +- [Revert finding correct user changes that were made at Wekan v4.16](https://github.com/wekan/wekan/commit/5eb378452761ad1d6d67a491316007fdf6dfd689). + Thanks to xet7. +- [Fix activities view on mobile devices](https://github.com/wekan/wekan/pull/3183). + Thanks to marc1006. +- [Add back checks about can user export CSV/TSV](https://github.com/wekan/wekan/commit/afe00d02cddf016a3ccc1ed9a98a7f10d3339f26). + Thanks to marc1006 and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.16 2020-06-17 Wekan release + +This release adds the following features: + +- [Add find-replace.sh script for development](https://github.com/wekan/wekan/commit/bda49ed60947e0438206b2f55119f5c5c132c734). + Thanks to xet7. + +and adds the following updates: + +- [Upgrade to Node 12.18.1](https://github.com/wekan/wekan/commit/b11ae567c9b2d16a115ea4f3f7f88e67d076f326). + Thanks to Node developers and xet7. + +and fixes the following bugs: + +- [OpenAPI: Fix jsdoc/operation matching](https://github.com/wekan/wekan/pull/3171). + Thanks to bentiss. +- Fix finding corrent user [Part1](https://github.com/wekan/wekan/pull/3180) and + [Part2](https://github.com/wekan/wekan/commit/f245b6b7faa29b4f276527daca48c305fe9689c1). + Thanks to salleman33 and xet7. +- [Try to prevent errors on CSV/TSV export](https://github.com/wekan/wekan/commit/b00db983c8506e0cdc9968e452c3c8025fc10776). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.15 2020-06-16 Wekan release + +This release fixes the following bugs: + +- Fix lint errors [Part1](https://github.com/wekan/wekan/commit/f1587753cb0bba38e4b1df2e0300d3dc2826da72) and + [Part2](https://github.com/wekan/wekan/commit/e6629779f77676eadfe4465c407f0bee0ec64061). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.14 2020-06-16 Wekan release + +This release adds the following new features: + +- [Add user option to hide finished checklist items. Strikethrough checked items](https://github.com/wekan/wekan/pull/3167). + Thanks to marc1006. +- [Added the possibility to start a vote via API edit_card. And added some better visibility to see what was voted](https://github.com/wekan/wekan/pull/3170). + Thanks to NicoP-S. + +and adds the following updates: + +- [Update dependencies](https://github.com/wekan/wekan/commit/8f34cdc279602e97085e0a504f7716495349f83c). + Thanks to xet7. + +and fixes the following bugs: + +- [Fix infinite scrolling for activities](https://github.com/wekan/wekan/pull/3168). + Thanks to marc1006. +- [Remove top and bottom margin for hidden checklist items](https://github.com/wekan/wekan/pull/3172). + Thanks to marc1006. +- [Alignment and spacing of minicard labels](https://github.com/wekan/wekan/pull/3174). + Thanks to hgustafsson. +- [Fix: Unable to delete a custom field in a board](https://github.com/wekan/wekan/commit/3b2b1087447bc8613baa8254bfec55e3d485bdc4). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.13 2020-06-09 Wekan release + +This release adds the following updates: + +- [OpenShift template updates](https://github.com/wekan/wekan/pull/3158), Thanks to jimmyjones2: + 1) Remove status fields (this is created by Kubernetes at run time) + 2) The latest MongoDB by default available with OpenShift is 3.6 + 3) Change MongoDB service name to contain wekan to avoid potentially conflicting with other mongodb instances in the same project. + +and fixes the following bugs: + +- [Copy the labels only if the target board is different](https://github.com/wekan/wekan/pull/3154). + Thanks to marc1006. +- [Fix condition whether a card is in list](https://github.com/wekan/wekan/pull/3165). + Thanks to marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.12 2020-06-08 Wekan release + +This release fixes the following CRITICAL SECURITY VULNERABILITIES: + +- Fix XSS bug reported 2020-05-24 by [swsjona](https://twitter.com/swsjona): + [Part 1](https://github.com/wekan/wekan/commit/1f85b25549b50602380f1745f19e5fe44fe36d6f), + [Part 2](https://github.com/wekan/wekan/commit/fb44df981581354bf23a6928427ad2bf73c4550f), + [Part 3](https://github.com/wekan/wekan/commit/99f68f36b028d6c75acf2e5b83585b1acee65f97), + [Part 4](https://github.com/wekan/wekan/commit/8a622ec7c3043bf8f34399ef34563e6a9a19dcd8). + Logged in users could run javascript in input fields. This was partially fixed at v3.85, + but at some fields XSS was still possible. This affects at least Wekan versions v3.12-v4.12. + After this fix, Javascript in input fields is not executed. + Thanks to swsjona, marc1006 and xet7. + +and adds the following new features: + +- Change default view to Swimlanes: + [Part 1](https://github.com/wekan/wekan/commit/8c3322f9a93c321e8a2cc5cfcd4b1d6316a5fb7c), + [Part 2](https://github.com/wekan/wekan/commit/61e682470cdaef42cce2d74b41fb752cfc61848b), + [Part 3 Change dropdown order to Swimlanes/Lists/Calendar](https://github.com/wekan/wekan/commit/7f6d500cbec15496ae357b05b9df3f10e51ed1f1), + [Part 4.1. Public board default view to Swimlane. Part 4.2. When changing Public board + view (sets view cookie), also reload page so view is changed + immediately](https://github.com/wekan/wekan/commit/39519d1cc944c567837be0f88ab4a037e2144c61). + Thanks to xet7. +- [Use markdown in Swimlane titles](https://github.com/wekan/wekan/commit/6b22f96313354b45b851b93c25aa392bbe346bdb). + Thanks to xet7. + +and adds the following updates: + +- [Update minifier-css](https://github.com/wekan/wekan/commit/cb1e91fee83eaad1e926c288c0abfc1e4f2a8bd4). + Thanks to xet7. + +and fixes the following bugs: + +- Fix indent [Part1](https://github.com/wekan/wekan/commit/415e94d187ffcb9a4afaecc5c6960a50a87ca7eb), + [Part 2](https://github.com/wekan/wekan/commit/96494bacf550cde65598e6d59199517f311aa33d). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.11 2020-06-04 Wekan release + +This release adds the following new platforms: + +- [Using arm64 bundle on Raspberry Pi OS arm64 with MongoDB 4.2.x for RasPi3 and + RasPi4](https://github.com/wekan/wekan/wiki/Raspberry-Pi#raspberry-pi-os-arm64-with-mongodb-42x). + Thanks to Raspberry Pi OS devs, MongoDB devs and xet7. +- [s390x RHEL 8](https://github.com/wekan/wekan/wiki/s390x#rhel-8). + Thanks to IBM, Red Hat Linux, Linux Foundation and xet7. + +and adds the following updates: + +- [Upgrade to Node v12.18.0](https://github.com/wekan/wekan/commit/d9d451a206cabe7f6ca8ad5d35eb76443198e4c1). + Thanks to Node developers and xet7. +- [Update `markedjs` package](https://github.com/wekan/wekan/pull/3149). + Thanks to marc1006. +- [Add fibers](https://github.com/wekan/wekan/commit/cd49018306f826fff37b7024dfde9de05d88b620). + Thanks to xet7. + +and adds the following new features: + +- [Add Calendar Month Event List view](https://github.com/wekan/wekan/commit/f73ea218eefba3f0d6c642849dfede9e03052d25). + Thanks to xet7. +- [Added dates & assignees to REST API calls](https://github.com/wekan/wekan/pull/3146). + Thanks to GitGramm. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.10 2020-05-30 Wekan release + +This release adds the following new features: + +- [Added an API to get the cards for a specific custom field value](https://github.com/wekan/wekan/pulls/3131). + Thanks to gvespignani70. + +and adds the following updates: + +- [Upgrade to Node v12.17.0](https://github.com/wekan/wekan/commit/3ade9d95a69b269c345127e1755e1b539dc07263). + Thanks to Node developers and xet7. + +and fixes the following bugs: + +- [Fix email verification in `sendSMTPTestEmail`](https://github.com/wekan/wekan/pull/3135). + Thanks to marc1006. +- [Try to Fix Registration broken "Templates board id is required" with ugly hack. If it works, ugly becomes + beautiful](https://github.com/wekan/wekan/pull/3140). + Thanks to marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.09 2020-05-27 Wekan release + +This release fixes the following bugs: + +- [Fix vote export & export/import currency custom field to CSV/TSV](https://github.com/wekan/wekan/pull/3128). + Thanks to brymut. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.08 2020-05-26 Wekan release + +This release adds the following new features: + +- [Add the 'Currency' Custom Field type](https://github.com/wekan/wekan/pull/3123). + Thanks to habenamare. + +and adds the following updates: + +- [Add some changes to Modern theme](https://github.com/wekan/wekan/commit/6a1bc167cf10e75d61b3196db9eac2978d70ad8e). + Thanks to jeroenstoker and xet7. + +and fixes the following bugs: + +- [Fix typo that caused parse error](https://github.com/wekan/wekan/commit/351d9d0c9577c9d543d543bc12a51388b0141324). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.07 2020-05-26 Wekan release + +This release fixes the following bugs: + +- [Fix move selection](https://github.com/wekan/wekan/pull/3120). + Thanks to marc1006. +- [Fix Python API generation](https://github.com/wekan/wekan/pull/3121). + Thanks to marc1006. +- [Fix default value of `sort`](https://github.com/wekan/wekan/pull/3122). + Thanks to marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.06 2020-05-25 Wekan release + +This release fixes the following bugs: + +- [Fix Card export CSV, check for vote + undefined](https://github.com/wekan/wekan/commit/8eafa1ac66fdcf5fb5f0a95aa6cfee454ddad67f). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.05 2020-05-25 Wekan release + +This release adds the following new features: + +- [Import/Export Custom Fields in CSV/TSV](https://github.com/wekan/wekan/pull/3115). + Thanks to brymut. + +and adds the following updates: + +- [Update packages](https://github.com/wekan/wekan/commit/3b44acd87c35340bf9fe5d210f4402f1b1a1dfdf). + Thanks to xet7. + +and fixes the following bugs: + +- Try to fix Snap [1](https://github.com/wekan/wekan/commit/6fad68b9b9afd8de7074037d73eeac40f6a3f7c1), [2](https://github.com/wekan/wekan/commit/b737adfcdfc9b8084a7eb84420a89c014bbec1fb). Later reverted those like other ostrio-files changes too. + Thanks to xet7. +- [Add default attachments store path /var/snap/wekan/common/uploads where attachments will be + stored](https://github.com/wekan/wekan/commit/c61a126c8bcb25a1eda0203b89c990ae31de7a70). + Thanks to xet7. +- [Make scrollParentContainer() more robust as it's used in a timeout callback. Example exception: Exception in setTimeout callback: TypeError: Cannot read property 'parentComponent' of null. Probably there is a better fix for this](https://github.com/wekan/wekan/commit/d5fbd50b760b1d3b84b5b4e8af3a8ed7608e2918). + Thanks to marc1006. +- [Fix error link not available. Fixes: Exception in template helper: TypeError: Cannot read property 'link' of +undefined](https://github.com/wekan/wekan/commit/b7105d7b5712dcdbf9dadebfddaba7691810da5c). + Thanks to marc1006. +- [Fix minicard cover functionality. Otherwise, if `this.coverId` is undefined then `Attachments.findOne()` would return any +attachment](https://github.com/wekan/wekan/commit/66d35a15280795b76a81c3e59cebbd2a29e4dff8). + Thanks to marc1006. +- [Some fixes suggested by deepcode.ai](https://github.com/wekan/wekan/pull/3112). + Thanks to marc1006. +- [Sorry marc1006, I had to revert deepcode.ai arrow function fixes because Python API docs generator does not work all when code has arrow functions](https://github.com/wekan/wekan/commit/f9018fc3a87080d8d97c371e29a8f3f0a20ca932). + Thanks to xet7. +- [Move In Progress ostrio-files changes to separate branch, and revert ostrio-files changes, so that: + Export to CSV/TSV with custom fields works, Attachments are not exported to disk, + It is possible to build arm64/s390x versions + again](https://github.com/wekan/wekan/commit/d52affe65893f17bab59bb43aa9f5afbb54993d3). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.04 2020-05-24 Wekan release + +Please use v4.05 or newer instead, that works better. + +This release adds the following features: + +- [Found Time Tracking GPLv3 software "Titra" with integration to Wekan](https://github.com/wekan/wekan/wiki/Time-Tracking). + Thanks to willhseitz. +- [Theme: Natural](https://github.com/wekan/wekan/pull/3098). + You can select it from Board Settings / Change color / natural. + Thanks to compumatter and helioguardabaxo. +- [Theme: Modern](https://github.com/wekan/wekan/pull/3106). + Thanks to jeroenstoker com and helioguardabaxo. +- [Export board to HTML static page .zip archive](https://github.com/wekan/wekan/pull/3043). + Thanks to Lewiscowles1986. + +and fixes the following bugs: + +- [Change the swimlaneid of a card only if a new target swimlaneid is selected](https://github.com/wekan/wekan/pull/3108). + Thanks to marc1006. +- [Set '*' as default value for swimlane and list name in card move action](https://github.com/wekan/wekan/pull/3109). + Thanks to hickorysb and marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.03 2020-05-16 Wekan release + +This release adds the following features: + +- [Theme: Clearblue](https://github.com/wekan/wekan/pull/3093). + You can select it from Board Settings / Change color / clearblue. + Thanks to CidKramer. + +and fixes the following bugs: + +- [Fix Can't Scroll on All Boards on mobile phone. Added drag handles](https://github.com/wekan/wekan/issues/3096). + Thanks to xet7. +- [Try to fix Sandstorm Wekan Export menu](https://github.com/wekan/wekan/commit/1ac11d92ba8f38981c87db25e5b5e1fa2adb6968). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.02 2020-05-15 Wekan release + +This release adds the following server platforms: + +- [Android arm64/x64](https://github.com/wekan/wekan/wiki/Android). + Thanks to xet7. + +and adds the following features: + +- [Install Wekan to mobile homescreen icon and use fullscreen + PWA](https://github.com/wekan/wekan/commit/8d5adc04645e3e71423f16869f39b8d79969bccd). + [Docs for iOS and Android at wiki PWA page](https://github.com/wekan/wekan/wiki/PWA). + Thanks to xet7. +- [Add options to rebuild-wekan.sh to run Meteor in development mode where after + file change it rebuilds](https://github.com/wekan/wekan/commit/5f915ef966170ea7baca7ddeb11319bc08a26fef). + Thanks to xet7. + +and adds the following updates: + +- [Update dependencies](https://github.com/wekan/wekan/commit/75bdd33fda58ea0233f5b38c466bcb1a9b0406ab). + Thanks to xet7. + +and adds the following translations: + +- [Add Spanish (Chile)](https://github.com/wekan/wekan/commit/96507e6777ed77a324eaec9799c5b46b0d25ad26). + Thanks to isos, Transifex user. + +and fixes the following bugs: + +- [Fix Deleting linked card makes board not load](https://github.com/wekan/wekan/issues/2785). + Thanks to marc1006 and xet7. +- [Fix getStartDayOfWeek once again](https://github.com/wekan/wekan/pull/3061). + Thanks to marc1006. +- [Fix shortcuts list and support card shortcuts when hovering + a card](https://github.com/wekan/wekan/pull/3066). + Thanks to marc1006. +- [Add white-space:normal to copy-to-clipboard button in card + details](https://github.com/wekan/wekan/pull/3075). + Thanks to helioguardabaxo. +- [Fix avatar-image class](https://github.com/wekan/wekan/pull/3083). + Thanks to krupupakku. +- [Fix Swimlanes ID missing in new boards](https://github.com/wekan/wekan/pull/3088). + Thanks to krupupakku. +- [Fix REST API so Create card does now allow an empty member + list](https://github.com/wekan/wekan/pull/3084). + Thanks to wackazong. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.01 2020-04-28 Wekan release + +This release adds the following updates: + +- [Upgrade to Node v12.16.3](https://github.com/wekan/wekan/commit/1d89e96dd101c11913f1acdd6d16b5650eaf18a7). + Thanks to Node developers and xet7. + +and fixes the following bugs: + +- [Fix Docker builds](https://github.com/wekan/wekan/commit/280e66947e3afa878c41e876cf827ebcec81a2c6). + Thanks to xet7. +- [Fix Cards and Users API docs at https://wekan.github.io/api/ not generated because of + syntax error and new Javascript syntax](https://github.com/wekan/wekan/commit/9ae20a3f51e63c29f536e2f5b3e66a2c7d88c691). + Wekan uses wekan/releases/generate-docs*.sh Python code to generate OpenAPI docs, + it did not show any errors while generating docs, only left out parts of API docs. + This affected Wekan versions v3.94-v4.00. + Thanks to pvcon13 and xet7. +- [Fix list header height when cards count is shown](https://github.com/wekan/wekan/pull/3056). + Thanks to marc1006. +- [Smaller height for Add Board button](https://github.com/wekan/wekan/commit/6afc9259f084717a0cc3ce6d66979fd7c1471939). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v4.00 2020-04-27 Wekan release + +This release fixes the following bugs: + +- [Make sure that the board header buttons fit into one line even for devices with 360px width + resolution](https://github.com/wekan/wekan/pull/3052). + Thanks to marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.99 2020-04-27 Wekan release + +This release fixes the following bugs: + +- [Fix Boards are very hard to tap in mobile](https://github.com/wekan/wekan/pull/3051). + Thanks to marc1006. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.98 2020-04-25 Wekan release + +News: + +- There is now many mobile and desktop webbrowser fixes. Please test does your + favourite Javascript enabled webbrowser work, and add issues if something + does not work, and there is no existing issue about that yet. +- Desktop browser mode has setting for Show/Hide drag handles: + top right click username / Change Settings / Show desktop drag handles. + You can request desktop website also at mobile webbrowsers on Android. + At iOS requesting desktop website did not seem to work yet. +- At iOS Safari and Chrome, to see swimlane buttons you need to scroll to right. + Fixes to this and other issues are welcome as pull request. + +This release adds the following new features: + +- [Pre-fill the title of checklists (Trello-style)](https://github.com/wekan/wekan/pull/3030). + Thanks to boeserwolf. +- [Implement option to change the first day of the week in user settings](https://github.com/wekan/wekan/pull/3032). + Thanks to marc1006. +- [Add babel to build chain and linter. Enables fancy Javascript language + features like optional chaining, for developer happiness](https://github.com/wekan/wekan/pull/3034). + Thanks to boeserwolf. +- [Use only one 'Apply' button for applying the user settings](https://github.com/wekan/wekan/pull/3039). + Thanks to marc1006. +- [Allow variable height for board list items. Allow words in title/description to be able to break + and wrap onto the next line](https://github.com/wekan/wekan/pull/3046). + Thanks to marc1006. + +and adds the following updates: + +- [Upgrade to Meteor 1.10.2](https://github.com/wekan/wekan/commit/d1f98d0c472fb41e25fb29a9a6f6dae7db003f6f). + Thanks to Meteor developers and xet7. +- [Set Snap MongoDB compatibility to 4.2 according to Meteor ChangeLog](https://github.com/wekan/wekan/commit/7de18eccea3854db3be6197bf21afbfd3ddb65a6). + Thanks to xet7. + +and fixes the following bugs: + +- [Multiple lint issue fixes](https://github.com/wekan/wekan/pull/3031). + Thanks to marc1006. +- [Fix lint errors in lint error fix](https://github.com/wekan/wekan/commit/9e95c06415e614e587d684ff9660cc53c5f8c8d3). + Thanks to xet7. +- [Fix getStartDayOfWeek function](https://github.com/wekan/wekan/pull/3038). + Thanks to marc1006 and boeserwolf. +- Improve mobile devices support [Part1](https://github.com/wekan/wekan/pull/3040) and [Part2](https://github.com/wekan/wekan/pull/3045). + Thanks to marc1006. +- [Fix Wekan not load at all in Firefox v.68 for Android](https://github.com/wekan/wekan/commit/1235363465b824d26129d4aa74a4445f362c1a73). + Thanks to xet7. +- [Fix comment typo in docker-compose.yml](https://github.com/wekan/wekan/pull/3044). + Thanks to VictorioBerra. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.97 2020-04-19 Wekan release + +This release adds the following new features: + +- [Sortable boards](https://github.com/wekan/wekan/pull/3027). + Thanks to boeserwolf. +- [Added dockerfiles for multi-arch builds and manifest](https://github.com/wekan/wekan/pull/3023). + [In Progress](https://github.com/wekan/wekan/issues/2999). + Thanks to brokencode64. +- [Make linked card clickable](https://github.com/wekan/wekan/pull/3025). + Thanks to boeserwolf. + +and fixes the following bugs: + +- [Fix using checklists on mobile and iPad](https://github.com/wekan/wekan/pull/3019). + Thanks to devinsm. +- [Improve card layout on mobile devices](https://github.com/wekan/wekan/pull/3024). + Thanks to marc1006. +- [Make OCP OAuth work with Openshift 4.x](https://github.com/wekan/wekan/pull/3020). + Thanks to ckavili. +- [Remove old warning from Sandstorm import board data loss, because bug has been already + fixed](https://github.com/wekan/wekan/commit/960fe5163b6a2f7c3dca03b5e31d69611b49f079). + Thanks to aputsiaq and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.96 2020-04-15 Wekan release + +This release adds the following Sandstorm updates: + +- This is the first Sandstorm Wekan release that uses newest Meteor 1.10.1 and Node 12.x. + Now all Wekan platforms use newest Meteor and Node 12.x LTS. + Thanks to kentonv and xet7. +- [Fix capnp workaround to work with newest Meteor and + Node 12.x](https://github.com/wekan/wekan/commit/b2d546579c4957352c29b36c0c8a4a08b944dbb4). + Thanks to kentonv. +- [Update Sandstorm release script for newest Meteor and + Node 12.x](https://github.com/wekan/wekan/commit/c5f782976b971fa3f2323e80a013bbf6a49c0596). + Thanks to xet7. +- [Remove Meteor 1.8.x files because Sandstorm Wekan now uses newest + Meteor](https://github.com/wekan/wekan/commit/1a836969e10215bad47ac56a9b0d9de801b66fd2). + Thanks to xet7. + +and adds the following new features: + +- [Hide password auth with environment variable PASSWORD_LOGIN_ENABLED=false](https://github.com/wekan/wekan/pull/3014). + Snap example: `sudo snap set wekan password-login-enabled='false'` . + Thanks to salleman33. + +and fixes the following bugs: + +- [Fix Board admins can not clone or archive their boards at All Boards + page](https://github.com/wekan/wekan/pull/3013). + Thanks to salleman33. +- [Fix `

` margin in card labels](https://github.com/wekan/wekan/pull/3015). + Thanks to boeserwolf. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.95 2020-04-12 Wekan release + +This release adds the following new features: + +- [Add gitpod config](https://github.com/wekan/wekan/pull/3009). + This adds support for Gitpod.io, a free automated + dev environment that makes contributing and generally working on GitHub + projects much easier. It allows anyone to start a ready-to-code dev + environment for any branch, issue and pull request with a single click. + Thanks to juniormendonca. +- [Public boards overview](https://github.com/wekan/wekan/pull/3008). + Thanks to NicoP-S. + +and fixes the following bugs: + +- [Fix styling issue in notifications drawer](https://github.com/wekan/wekan/pull/3012). + Thanks to boeserwolf. +- [Fix error in notifications cleanup cron](https://github.com/wekan/wekan/pull/3010). + Thanks to jtbairdsr. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.94 2020-04-12 Wekan release + +This release adds the following new features: + +- [Public vote](https://github.com/wekan/wekan/pull/3006). + Thanks to NicoP-S. +- [Add robots.txt disallow all](https://github.com/wekan/wekan/commit/3fae5355d40055757bf4a5f0c503581195609720). + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.93 2020-04-10 Wekan release + +This release adds the following new features: + +- [Trello vote import & hide export button if with_api is + disabled](https://github.com/wekan/wekan/pull/3000). + Thanks to NicoP-S. +- [When adding a user to a board that has subtasks, also add user to the subtask + board](https://github.com/wekan/wekan/pull/3004). + Thanks to slvrpdr. + +and adds the following updates: + +- Upgrade to Node v12.16.2 [Part1](https://github.com/wekan/wekan/commit/6db717b9b384fe1491063e507b80e67791a07e3a) + and [Part2](https://github.com/wekan/wekan/commit/268d7fcb32186a902a84e7f6d80c50b1f3790bad). + Thanks to Node developers and xet7. + +and fixes the following bugs: + +- [Fix bug that prevents editing or deleting + comments](https://github.com/wekan/wekan/pull/3005). + Thanks to jtbairdsr. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.92 2020-04-09 Wekan release + +This release adds the following new features: + +- [Scheduler to clean up read notifications. Also added a button to manually remove all + read notifications, and a fix to prevent users form getting notifications for their own + actions](https://github.com/wekan/wekan/pull/2998). + Thanks to jtbairdsr. +- [Add setting](https://github.com/wekan/wekan/commit/5ebb47cb0ec7272894a37d99579ede872251f55c) + default [NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2](https://github.com/wekan/wekan/pull/2998) + to all Wekan platforms. + Thanks to xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + +# v3.91 2020-04-08 Wekan release + +This release adds the following new features: + +- [OpenShift: Route template added to helm chart for Openshift v4x + cluster](https://github.com/wekan/wekan/pull/2996). + Thanks to ckavili. +- [Filter by Assignee](https://github.com/wekan/wekan/pull/2997). + Thanks to daniel-eder. +- [Vote on Card](https://github.com/wekan/wekan/pull/2994). + Thanks to NicoP-S and xet7. + +Thanks to above GitHub users for their contributions and translators for their translations. + # v3.90 2020-04-06 Wekan release This release makes the following updates: @@ -38,7 +2948,7 @@ and fixes the following bugs: - [Fix start-wekan.sh MongoDB port to 27017](https://github.com/wekan/wekan/commit/c60a092fc0ed9fe15c417bcb443b1e3e3aaedf7e). Thanks to Keelan and xet7. - + Thanks to above GitHub users for their contributions and translators for their translations. # v3.87 2020-04-01 Wekan release @@ -78,7 +2988,7 @@ This release fixes the following bugs: @member mention not close card, and disabling clicking of @member mention](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325). Thanks to xet7 ! - + Thanks to above GitHub users for their contributions and translators for their translations. # v3.85 2020-03-23 Wekan release diff --git a/Dockerfile b/Dockerfile index 440d88da2..d3184c349 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,19 @@ -FROM ubuntu:rolling +FROM quay.io/wekan/ubuntu:groovy-20210115 LABEL maintainer="wekan" +# 2020-12-03: +# - Above Ubuntu base image copied from Docker Hub ubuntu:groovy-20201125.2 +# to Quay to avoid Docker Hub rate limits. + # Set the environment variables (defaults where required) # DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303 # ENV BUILD_DEPS="paxctl" +ARG DEBIAN_FRONTEND=noninteractive + ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \ DEBUG=false \ - NODE_VERSION=v12.16.1 \ - METEOR_RELEASE=1.10-rc.2 \ + NODE_VERSION=v12.22.3 \ + METEOR_RELEASE=1.10.2 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=latest \ @@ -15,6 +21,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build- ARCHITECTURE=linux-x64 \ SRC_PATH=./ \ WITH_API=true \ + RESULTS_PER_PAGE="" \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \ @@ -26,6 +33,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build- ATTACHMENTS_STORE_PATH="" \ MAX_IMAGE_PIXEL="" \ IMAGE_COMPRESS_RATIO="" \ + NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \ BIGEVENTS_PATTERN=NONE \ NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" \ NOTIFY_DUE_AT_HOUR_OF_DAY="" \ @@ -38,6 +46,8 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build- TRUSTED_URL="" \ WEBHOOKS_ATTRIBUTES="" \ OAUTH2_ENABLED=false \ + OAUTH2_CA_CERT="" \ + OAUTH2_ADFS_ENABLED=false \ OAUTH2_LOGIN_STYLE=redirect \ OAUTH2_CLIENT_ID="" \ OAUTH2_SECRET="" \ @@ -111,8 +121,24 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build- CORS_ALLOW_HEADERS="" \ CORS_EXPOSE_HEADERS="" \ DEFAULT_AUTHENTICATION_METHOD="" \ - SCROLLINERTIA="0" \ - SCROLLAMOUNT="auto" + PASSWORD_LOGIN_ENABLED=true \ + CAS_ENABLED=false \ + CAS_BASE_URL="" \ + CAS_LOGIN_URL="" \ + CAS_VALIDATE_URL="" \ + SAML_ENABLED=false \ + SAML_PROVIDER="" \ + SAML_ENTRYPOINT="" \ + SAML_ISSUER="" \ + SAML_CERT="" \ + SAML_IDPSLO_REDIRECTURL="" \ + SAML_PRIVATE_KEYFILE="" \ + SAML_PUBLIC_CERTFILE="" \ + SAML_IDENTIFIER_FORMAT="" \ + SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \ + SAML_ATTRIBUTES="" \ + ORACLE_OIM_ENABLED=false \ + WAIT_SPINNER="" # Copy the app to the image COPY ${SRC_PATH} /home/wekan/app @@ -250,11 +276,12 @@ RUN \ mkdir -p /home/wekan/.npm && \ chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \ #gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \ + chmod u+w *.json && \ gosu wekan:wekan npm install && \ gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \ - cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \ + #cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \ #rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \ - chown wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \ + #chown wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \ #Removed binary version of bcrypt because of security vulnerability that is not fixed yet. #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c @@ -267,8 +294,11 @@ RUN \ #find . -name "*phantomjs*" | xargs rm -rf && \ # cd /home/wekan/app_build/bundle/programs/server/ && \ + chmod u+w *.json && \ gosu wekan:wekan npm install && \ #gosu wekan:wekan npm install bcrypt && \ + # Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. + rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \ mv /home/wekan/app_build/bundle /build && \ \ # Put back the original tar diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8 new file mode 100644 index 000000000..c6cc8a920 --- /dev/null +++ b/Dockerfile.arm64v8 @@ -0,0 +1,77 @@ +FROM amd64/alpine:3.7 AS builder + +# Set the environment variables for builder +ENV QEMU_VERSION=v4.2.0-6 \ + QEMU_ARCHITECTURE=aarch64 \ + NODE_ARCHITECTURE=linux-arm64 \ + NODE_VERSION=v12.22.3 \ + WEKAN_VERSION=latest \ + WEKAN_ARCHITECTURE=arm64 + + # Install dependencies +RUN apk update && apk add ca-certificates outils-sha1 && \ + \ + # Download qemu static for our architecture + wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \ + \ + # Download wekan and shasum + wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \ + wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \ + # Verify wekan + grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \ + \ + # Unzip wekan + unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \ + \ + # Download node and shasums + wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \ + wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \ + \ + # Verify nodejs authenticity + grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | sha256sum -c - && \ + \ + # Extract node and remove tar.gz + tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz + +# Build wekan dockerfile +FROM arm64v8/ubuntu:19.10 +LABEL maintainer="wekan" + +# Set the environment variables (defaults where required) +ENV QEMU_ARCHITECTURE=aarch64 \ + NODE_ARCHITECTURE=linux-arm64 \ + NODE_VERSION=v12.22.3 \ + NODE_ENV=production \ + NPM_VERSION=latest \ + WITH_API=true \ + PORT=8080 \ + ROOT_URL=http://localhost \ + MONGO_URL=mongodb://127.0.0.1:27017/wekan + +# Copy qemu-static to image +COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin + +# Copy the app to the image +COPY --from=builder bundle /home/wekan/bundle + +# Copy +COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs + +RUN \ + set -o xtrace && \ + # Add non-root user wekan + useradd --user-group --system --home-dir /home/wekan wekan && \ + \ + # Install Node + ln -s /opt/nodejs/bin/node /usr/bin/node && \ + ln -s /opt/nodejs/bin/npm /usr/bin/npm && \ + mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \ + chown wekan --recursive /home/wekan/.config && \ + \ + # Install Node dependencies + npm install -g npm@${NPM_VERSION} + +EXPOSE $PORT +USER wekan + +CMD ["node", "/home/wekan/bundle/main.js"] diff --git a/README.md b/README.md index affbf5e8d..de4007d16 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wekan/wekan) + # Wekan - Open Source kanban [![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors) @@ -10,6 +12,7 @@ [![Project Dependencies](https://david-dm.org/wekan/wekan.svg "Project Dependencies")](https://david-dm.org/wekan/wekan) [![Code analysis at Open Hub](https://img.shields.io/badge/code%20analysis-at%20Open%20Hub-brightgreen.svg "Code analysis at Open Hub")](https://www.openhub.net/p/wekan) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619/badge)](https://bestpractices.coreinfrastructure.org/projects/4619) ## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan) @@ -18,21 +21,25 @@ New English strings of new features can be added as PRs to edge branch file weka ## [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues) -Please add most of your questions as GitHub issue: [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues). +Please add most of your questions as GitHub issue: [Wekan Feature Requests and Bugs](https://github.com/wekan/wekan/issues). It's better than at chat where details get lost when chat scrolls up. ## Chat -[![Wekan Chat][vanila_badge]][wekan_chat] - Most Wekan community and developers are here. Works on webbrowser -and PWA app that can be added as icon on Android and bookmark on iOS, used like native app. +[Discussions][discussions] - Wekan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues). [Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ) +## Docker: Please only use Docker release tags + +Note: With Docker, please don't use latest tag. Only use release tags. +See https://github.com/wekan/wekan/issues/3874 + ## FAQ -**NOTE**: +**NOTE**: - Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first -- Please don't feed the trolls and spammers that are mentioned in the FAQ :) +- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :) ## About Wekan @@ -50,7 +57,7 @@ that by providing one-click installation on various platforms. - Wekan is used in [most countries of the world](https://snapcraft.io/wekan). - Wekan largest user has 13k users using Wekan in their company. -- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 50 languages. +- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 63 languages. - [Features][features]: Wekan has real-time user interface. - [Platforms][platforms]: Wekan supports many platforms. Wekan is critical part of new platforms Wekan is currently being integrated to. @@ -62,7 +69,7 @@ that by providing one-click installation on various platforms. [More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists. - 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM. For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/master/docker-compose.yml): 3 frontend servers, - each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs. + each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs. - Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted. - SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off. Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported. @@ -112,8 +119,6 @@ with [Meteor](https://www.meteor.com). [translate_wekan]: https://www.transifex.com/wekan/wekan/ [open_source]: https://en.wikipedia.org/wiki/Open-source_software [free_software]: https://en.wikipedia.org/wiki/Free_software -[vanila_badge]: https://vanila.io/img/join-chat-button2.png -[wekan_chat]: https://community.vanila.io/wekan - +[discussions]: https://github.com/wekan/wekan/discussions [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large) diff --git a/SECURITY.md b/SECURITY.md index 03f5e7dae..b0ee6c4a3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,10 +1,10 @@ Security is very important to us. If you discover any issue regarding security, please disclose -the information responsibly by sending an email to security (at) 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. We thank you with a place at our hall of fame page, that is -at https://wekan.github.io/hall-of-fame . Others have just posted public GitHub issue, -so they are not at that hall-of-fame page. +at https://wekan.github.io/hall-of-fame ## How should reports be formatted? diff --git a/Stackerfile.yml b/Stackerfile.yml index 48a033a4f..117fd45c6 100644 --- a/Stackerfile.yml +++ b/Stackerfile.yml @@ -1,5 +1,5 @@ appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 -appVersion: "v3.90.0" +appVersion: "v5.38.0" files: userUploads: - README.md diff --git a/api.py b/api.py new file mode 100755 index 000000000..891f62aac --- /dev/null +++ b/api.py @@ -0,0 +1,291 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +# vi:ts=4:et + +# Wekan API Python CLI, originally from here, where is more details: +# https://github.com/wekan/wekan/wiki/New-card-with-Python3-and-REST-API + +try: + # python 3 + from urllib.parse import urlencode +except ImportError: + # python 2 + from urllib import urlencode + +import json +import requests +import sys + +arguments = len(sys.argv) - 1 + +if arguments == 0: + print("=== Wekan API Python CLI: Shows IDs for addcard ===") + print("AUTHORID is USERID that writes card.") + print("If *nix: chmod +x api.py => ./api.py users") + print("Syntax:") + print(" python3 api.py users # All users") + print(" python3 api.py boards USERID # Boards of USERID") + print(" python3 api.py board BOARDID # Info of BOARDID") + print(" python3 api.py swimlanes BOARDID # Swimlanes of BOARDID") + print(" python3 api.py lists BOARDID # Lists of BOARDID") + print(" python3 api.py list BOARDID LISTID # Info of LISTID") + print(" python3 api.py createlist BOARDID LISTTITLE # Create list") + print(" python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION") + print(" python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION") + print(" python3 api.py listattachments BOARDID # List attachments") +# TODO: +# print(" python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64") +# print(" python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file") +# print(" python3 api.py attachmentdownload BOARDID ATTACHMENTID # One attachment as file") +# print(" python3 api.py attachmentsdownload BOARDID # All attachments as files") + exit + +# ------- SETTINGS START ------------- + +# Username is your Wekan username or email address. +# OIDC/OAuth2 etc uses email address as username. + +username = 'testtest' + +password = 'testtest' + +wekanurl = 'http://localhost:4000/' + +# ------- SETTINGS END ------------- + +""" +EXAMPLE: + +python3 api.py + +OR: +chmod +x api.py +./api.py + +=== Wekan API Python CLI: Shows IDs for addcard === +AUTHORID is USERID that writes card. +Syntax: + python3 api.py users # All users + python3 api.py boards USERID # Boards of USERID + python3 api.py board BOARDID # Info of BOARDID + python3 api.py swimlanes BOARDID # Swimlanes of BOARDID + python3 api.py lists BOARDID # Lists of BOARDID + python3 api.py list BOARDID LISTID # Info of LISTID + python3 api.py createlist BOARDID LISTTITLE # Create list + python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION + python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION + python3 api.py listattachments BOARDID # List attachments + python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64 + python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file + +=== USERS === + +python3 api.py users + +=> abcd1234 + +=== BOARDS === + +python3 api.py boards abcd1234 + + +=== SWIMLANES === + +python3 api.py swimlanes dYZ + +[{"_id":"Jiv","title":"Default"} +] + +=== LISTS === + +python3 api.py lists dYZ + +[] + +There is no lists, so create a list: + +=== CREATE LIST === + +python3 api.py createlist dYZ 'Test' + +{"_id":"7Kp"} + +# python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION + +python3 api.py addcard ppg dYZ Jiv 7Kp 'Test card' 'Test description' + +=== LIST ATTACHMENTS WITH DOWNLOAD URLs ==== + +python3 api.py listattachments BOARDID + +""" + +# ------- API URL GENERATION START ----------- + +loginurl = 'users/login' +wekanloginurl = wekanurl + loginurl +apiboards = 'api/boards/' +apiattachments = 'api/attachments/' +apiusers = 'api/users' +e = 'export' +s = '/' +l = 'lists' +sw = 'swimlane' +sws = 'swimlanes' +cs = 'cards' +bs = 'boards' +atl = 'attachmentslist' +at = 'attachment' +ats = 'attachments' +users = wekanurl + apiusers + +# ------- API URL GENERATION END ----------- + +# ------- LOGIN TOKEN START ----------- + +data = {"username": username, "password": password} +body = requests.post(wekanloginurl, data=data) +d = body.json() +apikey = d['token'] + +# ------- LOGIN TOKEN END ----------- + +if arguments == 7: + + if sys.argv[1] == 'addcard': + # ------- WRITE TO CARD START ----------- + authorid = sys.argv[2] + boardid = sys.argv[3] + swimlaneid = sys.argv[4] + listid = sys.argv[5] + cardtitle = sys.argv[6] + carddescription = sys.argv[7] + cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + # Write to card + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + post_data = {'authorId': '{}'.format(authorid), 'title': '{}'.format(cardtitle), 'description': '{}'.format(carddescription), 'swimlaneId': '{}'.format(swimlaneid)} + body = requests.post(cardtolist, data=post_data, headers=headers) + print(body.text) + # ------- WRITE TO CARD END ----------- + +if arguments == 6: + + if sys.argv[1] == 'editcard': + + # ------- LIST OF BOARD START ----------- + boardid = sys.argv[2] + listid = sys.argv[3] + cardid = sys.argv[4] + newcardtitle = sys.argv[5] + newcarddescription = sys.argv[6] + edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid + print(edcard) + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + put_data = {'title': '{}'.format(newcardtitle), 'description': '{}'.format(newcarddescription)} + body = requests.put(edcard, data=put_data, headers=headers) + print("=== EDIT CARD ===\n") + body = requests.get(edcard, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- LISTS OF BOARD END ----------- + +if arguments == 3: + + if sys.argv[1] == 'createlist': + + # ------- CREATE LIST START ----------- + boardid = sys.argv[2] + listtitle = sys.argv[3] + list = wekanurl + apiboards + boardid + s + l + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + post_data = {'title': '{}'.format(listtitle)} + body = requests.post(list, data=post_data, headers=headers) + print("=== CREATE LIST ===\n") + print(body.text) + # ------- CREATE LIST END ----------- + + if sys.argv[1] == 'list': + + # ------- LIST OF BOARD START ----------- + boardid = sys.argv[2] + listid = sys.argv[3] + listone = wekanurl + apiboards + boardid + s + l + s + listid + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + print("=== INFO OF ONE LIST ===\n") + body = requests.get(listone, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- LISTS OF BOARD END ----------- + +if arguments == 2: + + # ------- BOARDS LIST START ----------- + userid = sys.argv[2] + boards = users + s + userid + s + bs + if sys.argv[1] == 'boards': + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + #post_data = {'userId': '{}'.format(userid)} + body = requests.get(boards, headers=headers) + print("=== BOARDS ===\n") + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- BOARDS LIST END ----------- + if sys.argv[1] == 'board': + + # ------- BOARD INFO START ----------- + boardid = sys.argv[2] + board = wekanurl + apiboards + boardid + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + body = requests.get(board, headers=headers) + print("=== BOARD ===\n") + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- BOARD INFO END ----------- + + if sys.argv[1] == 'swimlanes': + boardid = sys.argv[2] + swimlanes = wekanurl + apiboards + boardid + s + sws + # ------- SWIMLANES OF BOARD START ----------- + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + print("=== SWIMLANES ===\n") + body = requests.get(swimlanes, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- SWIMLANES OF BOARD END ----------- + + if sys.argv[1] == 'lists': + + # ------- LISTS OF BOARD START ----------- + boardid = sys.argv[2] + lists = wekanurl + apiboards + boardid + s + l + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + print("=== LISTS ===\n") + body = requests.get(lists, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- LISTS OF BOARD END ----------- + + if sys.argv[1] == 'listattachments': + + # ------- LISTS OF ATTACHMENTS START ----------- + boardid = sys.argv[2] + listattachments = wekanurl + apiboards + boardid + s + ats + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + print("=== LIST OF ATTACHMENTS ===\n") + body = requests.get(listattachments, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- LISTS OF ATTACHMENTS END ----------- + +if arguments == 1: + + if sys.argv[1] == 'users': + + # ------- LIST OF USERS START ----------- + headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} + print(users) + print("=== USERS ===\n") + body = requests.get(users, headers=headers) + data2 = body.text.replace('}',"}\n") + print(data2) + # ------- LIST OF USERS END ----------- diff --git a/client/00-startup.js b/client/00-startup.js new file mode 100644 index 000000000..4a717b67c --- /dev/null +++ b/client/00-startup.js @@ -0,0 +1,6 @@ +// PWA +if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register('/pwa-service-worker.js'); + }); +} diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index c86936a01..38825ab5d 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -15,11 +15,18 @@ template(name="cardActivities") each activityData in currentCard.activities +activity(activity=activityData card=card mode=mode) +template(name="editOrDeleteComment") + = ' - ' + a.js-open-inlined-form {{_ "edit"}} + = ' - ' + a.js-delete-comment {{_ "delete"}} + template(name="activity") .activity +userAvatar(userId=activity.user._id) p.activity-desc - +memberName(user=activity.user) + span.activity-member + +memberName(user=activity.user) //- attachment activity ------------------------------------------------- if($eq activity.activityType 'deleteAttachment') @@ -34,38 +41,38 @@ template(name="activity") //- board activity ------------------------------------------------------ if($eq mode 'board') if($eq activity.activityType 'createBoard') - | {{_ 'activity-created' boardLabel}}. + | {{{_ 'activity-created' boardLabelLink}}}. if($eq activity.activityType 'importBoard') - | {{{_ 'activity-imported-board' boardLabel sourceLink}}}. + | {{{_ 'activity-imported-board' boardLabelLink sourceLink}}}. if($eq activity.activityType 'addBoardMember') - | {{{_ 'activity-added' memberLink boardLabel}}}. + | {{{_ 'activity-added' memberLink boardLabelLink}}}. if($eq activity.activityType 'removeBoardMember') - | {{{_ 'activity-excluded' memberLink boardLabel}}}. + | {{{_ 'activity-excluded' memberLink boardLabelLink}}}. //- card activity ------------------------------------------------------- if($eq activity.activityType 'createCard') if($eq mode 'card') - | {{{_ 'activity-added' cardLabel activity.listName}}}. + | {{{_ 'activity-added' cardLabelLink (sanitize activity.listName)}}}. else - | {{{_ 'activity-added' cardLabel boardLabel}}}. + | {{{_ 'activity-added' cardLabelLink boardLabelLink}}}. if($eq activity.activityType 'importCard') - | {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}. + | {{{_ 'activity-imported' cardLink boardLabelLink sourceLink}}}. if($eq activity.activityType 'moveCard') - | {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}. + | {{{_ 'activity-moved' cardLabelLink (sanitize activity.oldList.title) (sanitize activity.list.title)}}}. if($eq activity.activityType 'moveCardBoard') - | {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}. + | {{{_ 'activity-moved' cardLink (sanitize activity.oldBoardName) (sanitize activity.boardName)}}}. if($eq activity.activityType 'archivedCard') | {{{_ 'activity-archived' cardLink}}}. if($eq activity.activityType 'restoredCard') - | {{{_ 'activity-sent' cardLink boardLabel}}}. + | {{{_ 'activity-sent' cardLink boardLabelLink}}}. //- checklist activity -------------------------------------------------- if($eq activity.activityType 'addChecklist') @@ -75,7 +82,7 @@ template(name="activity") +viewer = activity.checklist.title else - a.activity-checklist(href="{{ activity.card.absoluteUrl }}") + a.activity-checklist(href="{{ activity.card.originRelativeUrl }}") +viewer = activity.checklist.title @@ -83,25 +90,25 @@ template(name="activity") | {{{_ 'activity-checklist-removed' cardLink}}}. if($eq activity.activityType 'completeChecklist') - | {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}. + | {{{_ 'activity-checklist-completed' (sanitize activity.checklist.title) cardLink}}}. if($eq activity.activityType 'uncompleteChecklist') - | {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}. + | {{{_ 'activity-checklist-uncompleted' (sanitize activity.checklist.title) cardLink}}}. if($eq activity.activityType 'checkedItem') - | {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}. + | {{{_ 'activity-checked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}. if($eq activity.activityType 'uncheckedItem') - | {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}. + | {{{_ 'activity-unchecked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}. if($eq activity.activityType 'addChecklistItem') - | {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}. - .activity-checklist(href="{{ activity.card.absoluteUrl }}") + | {{{_ 'activity-checklist-item-added' (sanitize activity.checklist.title) cardLink}}}. + .activity-checklist(href="{{ activity.card.originRelativeUrl }}") +viewer = activity.checklistItem.title if($eq activity.activityType 'removedChecklistItem') - | {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}. + | {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}. //- comment activity ---------------------------------------------------- if($eq mode 'card') @@ -118,11 +125,10 @@ template(name="activity") +viewer = activity.comment.text span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} - if ($eq currentUser._id activity.comment.userId) - = ' - ' - a.js-open-inlined-form {{_ "edit"}} - = ' - ' - a.js-delete-comment {{_ "delete"}} + if($eq currentUser._id activity.comment.userId) + +editOrDeleteComment + else if currentUser.isBoardAdmin + +editOrDeleteComment if($eq activity.activityType 'deleteComment') | {{{_ 'activity-deleteComment' currentData.commentId}}}. @@ -133,41 +139,68 @@ template(name="activity") //- if we are not in card mode we only display a summary of the comment if($eq activity.activityType 'addComment') | {{{_ 'activity-on' cardLink}}} - a.activity-comment(href="{{ activity.card.absoluteUrl }}") + a.activity-comment(href="{{ activity.card.originRelativeUrl }}") +viewer = activity.comment.text + //- date activity ------------------------------------------------ + if($eq mode 'card') + if($eq activity.activityType 'a-receivedAt') + | {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}. + + if($eq activity.activityType 'a-startAt') + | {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}. + + if($eq activity.activityType 'a-dueAt') + | {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}. + + if($eq activity.activityType 'a-endAt') + | {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}. + + if($eq mode 'board') + if($eq activity.activityType 'a-receivedAt') + | {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}. + + if($eq activity.activityType 'a-startAt') + | {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}. + + if($eq activity.activityType 'a-dueAt') + | {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}. + + if($eq activity.activityType 'a-endAt') + | {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}. + //- customField activity ------------------------------------------------ if($eq mode 'board') if($eq activity.activityType 'createCustomField') | {{_ 'activity-customfield-created' customField}}. if($eq activity.activityType 'setCustomField') - | {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}. + | {{{_ 'activity-set-customfield' (sanitize lastCustomField) (sanitize lastCustomFieldValue) cardLink}}}. if($eq activity.activityType 'unsetCustomField') - | {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}. + | {{{_ 'activity-unset-customfield' (sanitize lastCustomField) cardLink}}}. //- label activity ------------------------------------------------------ if($eq activity.activityType 'addedLabel') - | {{{_ 'activity-added-label' lastLabel cardLink}}}. + | {{{_ 'activity-added-label' (sanitize lastLabel) cardLink}}}. if($eq activity.activityType 'removedLabel') - | {{{_ 'activity-removed-label' lastLabel cardLink}}}. + | {{{_ 'activity-removed-label' (sanitize lastLabel) cardLink}}}. //- list activity ------------------------------------------------------- if($neq mode 'card') if($eq activity.activityType 'createList') - | {{{_ 'activity-added' listLabel boardLabel}}}. + | {{{_ 'activity-added' (sanitize listLabel) boardLabelLink}}}. if($eq activity.activityType 'importList') - | {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}. + | {{{_ 'activity-imported' (sanitize listLabel) boardLabelLink sourceLink}}}. if($eq activity.activityType 'removeList') - | {{{_ 'activity-removed' activity.title boardLabel}}}. + | {{{_ 'activity-removed' (sanitize activity.title) boardLabelLink}}}. if($eq activity.activityType 'archivedList') - | {{_ 'activity-archived' listLabel}}. + | {{_ 'activity-archived' (sanitize listLabel)}}. //- member activity ---------------------------------------------------- if($eq activity.activityType 'joinMember') @@ -185,15 +218,15 @@ template(name="activity") //- swimlane activity -------------------------------------------------- if($neq mode 'card') if($eq activity.activityType 'createSwimlane') - | {{{_ 'activity-added' activity.swimlane.title boardLabel}}}. + | {{_ 'activity-added' (sanitize activity.swimlane.title) boardLabelLink}}. if($eq activity.activityType 'archivedSwimlane') - | {{_ 'activity-archived' activity.swimlane.title}}. + | {{_ 'activity-archived' (sanitize activity.swimlane.title)}}. //- I don't understand this part ---------------------------------------- if(currentData.timeKey) - | {{{_ activity.activityType }}} + | {{_ activity.activityType }} = ' ' i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }} if (currentData.timeOldValue) @@ -203,6 +236,6 @@ template(name="activity") i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }} = ' @' else if(currentData.timeValue) - | {{{_ activity.activityType currentData.timeValue}}} + | {{_ activity.activityType currentData.timeValue}} span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 36214e199..fa2628600 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -1,12 +1,15 @@ -const activitiesPerPage = 20; +import DOMPurify from 'dompurify'; + +const activitiesPerPage = 500; BlazeComponent.extendComponent({ onCreated() { // XXX Should we use ReactiveNumber? this.page = new ReactiveVar(1); this.loadNextPageLocked = false; - const sidebar = this.parentComponent(); // XXX for some reason not working - sidebar.callFirstWith(null, 'resetNextPeak'); + // TODO is sidebar always available? E.g. on small screens/mobile devices + const sidebar = Sidebar; + sidebar && sidebar.callFirstWith(null, 'resetNextPeak'); this.autorun(() => { let mode = this.data().mode; const capitalizedMode = Utils.capitalize(mode); @@ -27,6 +30,8 @@ BlazeComponent.extendComponent({ 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 @@ -41,23 +46,22 @@ BlazeComponent.extendComponent({ }); }); }, -}).register('activities'); - -BlazeComponent.extendComponent({ loadNextPage() { if (this.loadNextPageLocked === false) { this.page.set(this.page.get() + 1); this.loadNextPageLocked = true; } }, +}).register('activities'); +BlazeComponent.extendComponent({ checkItem() { const checkItemId = this.currentData().activity.checklistItemId; const checkItem = ChecklistItems.findOne({ _id: checkItemId }); return checkItem && checkItem.title; }, - boardLabel() { + boardLabelLink() { const data = this.currentData(); if (data.mode !== 'board') { return createBoardLink(data.activity.board(), data.activity.listName); @@ -65,10 +69,10 @@ BlazeComponent.extendComponent({ return TAPi18n.__('this-board'); }, - cardLabel() { + cardLabelLink() { const data = this.currentData(); if (data.mode !== 'card') { - return createCardLink(this.currentData().activity.card()); + return createCardLink(data.activity.card()); } return TAPi18n.__('this-card'); }, @@ -77,6 +81,30 @@ BlazeComponent.extendComponent({ return createCardLink(this.currentData().activity.card()); }, + receivedDate() { + const receivedDate = this.currentData().activity.card(); + if (!receivedDate) return null; + return receivedDate.receivedAt; + }, + + startDate() { + const startDate = this.currentData().activity.card(); + if (!startDate) return null; + return startDate.startAt; + }, + + dueDate() { + const dueDate = this.currentData().activity.card(); + if (!dueDate) return null; + return dueDate.dueAt; + }, + + endDate() { + const endDate = this.currentData().activity.card(); + if (!endDate) return null; + return endDate.endAt; + }, + lastLabel() { const lastLabelId = this.currentData().activity.labelId; if (!lastLabelId) return null; @@ -134,11 +162,15 @@ BlazeComponent.extendComponent({ { href: source.url, }, - source.system, + DOMPurify.sanitize(source.system, { + ALLOW_UNKNOWN_PROTOCOLS: true, + }), ), ); } else { - return source.system; + return DOMPurify.sanitize(source.system, { + ALLOW_UNKNOWN_PROTOCOLS: true, + }); } } return null; @@ -162,10 +194,10 @@ BlazeComponent.extendComponent({ href: attachment.url({ download: true }), target: '_blank', }, - attachment.name(), + DOMPurify.sanitize(attachment.name()), ), )) || - this.currentData().activity.attachmentName + DOMPurify.sanitize(this.currentData().activity.attachmentName) ); }, @@ -180,7 +212,7 @@ BlazeComponent.extendComponent({ { // XXX We should use Popup.afterConfirmation here 'click .js-delete-comment'() { - const commentId = this.currentData().commentId; + const commentId = this.currentData().activity.commentId; CardComments.remove(commentId); }, 'submit .js-edit-comment'(evt) { @@ -188,7 +220,7 @@ BlazeComponent.extendComponent({ const commentText = this.currentComponent() .getValue() .trim(); - const commentId = Template.parentData().commentId; + const commentId = Template.parentData().activity.commentId; if (commentText) { CardComments.update(commentId, { $set: { @@ -202,16 +234,23 @@ BlazeComponent.extendComponent({ }, }).register('activity'); +Template.activity.helpers({ + sanitize(value) { + return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true }); + }, +}); + function createCardLink(card) { + if (!card) return ''; return ( card && Blaze.toHTML( HTML.A( { - href: card.absoluteUrl(), + href: card.originRelativeUrl(), class: 'action-card', }, - card.title, + DOMPurify.sanitize(card.title, { ALLOW_UNKNOWN_PROTOCOLS: true }), ), ) ); @@ -225,10 +264,10 @@ function createBoardLink(board, list) { Blaze.toHTML( HTML.A( { - href: board.absoluteUrl(), + href: board.originRelativeUrl(), class: 'action-board', }, - text, + DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }), ), ) ); diff --git a/client/components/activities/activities.styl b/client/components/activities/activities.styl index f3b1acddc..3df2b7816 100644 --- a/client/components/activities/activities.styl +++ b/client/components/activities/activities.styl @@ -10,12 +10,16 @@ .activity margin: 0.5px 0 + padding: 6px 0; display: flex .member - width: 24px + width: 32px height: @width + .activity-member + font-weight: 700 + .activity-desc word-wrap: break-word overflow: hidden diff --git a/client/components/activities/comments.jade b/client/components/activities/comments.jade index 405778de0..4cddf4c23 100644 --- a/client/components/activities/comments.jade +++ b/client/components/activities/comments.jade @@ -1,7 +1,7 @@ template(name="commentForm") .new-comment.js-new-comment( class="{{#if commentFormIsOpen}}is-open{{/if}}") - +userAvatar(userId=currentUser._id) + +userAvatar(userId=currentUser._id noRemove=true) form.js-new-comment-form +editor(class="js-new-comment-input") | {{getUnsavedValue 'cardComment' currentCard._id}} diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index 50ca019b4..c69ec383e 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -3,6 +3,7 @@ const commentFormIsOpen = new ReactiveVar(false); BlazeComponent.extendComponent({ onDestroyed() { commentFormIsOpen.set(false); + $('.note-popover').hide(); }, commentFormIsOpen() { diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index d3e65bd8d..49f024f8d 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -3,11 +3,15 @@ BlazeComponent.extendComponent({ this.subscribe('archivedBoards'); }, + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + archivedBoards() { return Boards.find( { archived: true }, { - sort: ['title'], + sort: { archivedAt: -1, modifiedAt: -1 }, }, ); }, diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 76a85d877..c11d0bd22 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -15,7 +15,7 @@ template(name="board") template(name="boardBody") .board-wrapper(class=currentBoard.colorClass) +sidebar - .board-canvas.js-swimlanes.js-perfect-scrollbar( + .board-canvas.js-swimlanes( class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}" class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}" class="{{#if draggingActive.get}}is-dragging-active{{/if}}") diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index e70a9f67a..900931d05 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,7 +1,5 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); const subManager = new SubsManager(); -const { calculateIndex, enableClickOnTouch } = Utils; +const { calculateIndex } = Utils; const swimlaneWhileSortingHeight = 150; BlazeComponent.extendComponent({ @@ -191,21 +189,18 @@ BlazeComponent.extendComponent({ }, }); - // ugly touch event hotfix - enableClickOnTouch('.js-swimlane:not(.placeholder)'); - this.autorun(() => { let showDesktopDragHandles = false; currentUser = Meteor.user(); if (currentUser) { showDesktopDragHandles = (currentUser.profile || {}) .showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { showDesktopDragHandles = true; } else { showDesktopDragHandles = false; } - if (!Utils.isMiniScreen() && showDesktopDragHandles) { + if (Utils.isMiniScreen() || showDesktopDragHandles) { $swimlanesDom.sortable({ handle: '.js-swimlane-header-handle', }); @@ -215,9 +210,13 @@ BlazeComponent.extendComponent({ }); } - // Disable drag-dropping if the current user is not a board member or is miniscreen - $swimlanesDom.sortable('option', 'disabled', !userIsMember()); - $swimlanesDom.sortable('option', 'disabled', Utils.isMiniScreen()); + // Disable drag-dropping if the current user is not a board member + //$swimlanesDom.sortable('option', 'disabled', !userIsMember()); + $swimlanesDom.sortable( + 'option', + 'disabled', + !Meteor.user().isBoardAdmin(), + ); }); function userIsMember() { @@ -241,7 +240,9 @@ BlazeComponent.extendComponent({ if (currentUser) { return (currentUser.profile || {}).boardView === 'board-view-swimlanes'; } else { - return cookies.get('boardView') === 'board-view-swimlanes'; + return ( + window.localStorage.getItem('boardView') === 'board-view-swimlanes' + ); } }, @@ -250,7 +251,7 @@ BlazeComponent.extendComponent({ if (currentUser) { return (currentUser.profile || {}).boardView === 'board-view-lists'; } else { - return cookies.get('boardView') === 'board-view-lists'; + return window.localStorage.getItem('boardView') === 'board-view-lists'; } }, @@ -259,7 +260,7 @@ BlazeComponent.extendComponent({ if (currentUser) { return (currentUser.profile || {}).boardView === 'board-view-cal'; } else { - return cookies.get('boardView') === 'board-view-cal'; + return window.localStorage.getItem('boardView') === 'board-view-cal'; } }, @@ -327,7 +328,7 @@ BlazeComponent.extendComponent({ header: { left: 'title today prev,next', center: - 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,timelineMonth timelineYear', + 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth', right: '', }, // height: 'parent', nope, doesn't work as the parent might be small @@ -359,7 +360,7 @@ BlazeComponent.extendComponent({ end: end || card.endAt, allDay: Math.abs(end.getTime() - start.getTime()) / 1000 === 24 * 3600, - url: FlowRouter.url('card', { + url: FlowRouter.path('card', { boardId: currentBoard._id, slug: currentBoard.slug, cardId: card._id, @@ -421,7 +422,7 @@ BlazeComponent.extendComponent({ if (currentUser) { return (currentUser.profile || {}).boardView === 'board-view-cal'; } else { - return cookies.get('boardView') === 'board-view-cal'; + return window.localStorage.getItem('boardView') === 'board-view-cal'; } }, }).register('calendarView'); diff --git a/client/components/boards/boardColors.styl b/client/components/boards/boardColors.styl index 3be9c0c39..19246356b 100644 --- a/client/components/boards/boardColors.styl +++ b/client/components/boards/boardColors.styl @@ -15,7 +15,8 @@ setBoardColor(color) .is-selected .minicard border-left: 3px solid color - button[type=submit].primary, input[type=submit].primary + button[type=submit].primary, input[type=submit].primary, + .sidebar .sidebar-content .sidebar-btn background-color: darken(color, 20%) &.pop-over .pop-over-list li a:not(.disabled):hover, @@ -293,3 +294,770 @@ setBoardColor(color) //.header-quick-access // backgroud-color: #568ba2 + + +/* + Alternate "Clear" Styling +*/ +setBoardClear(color1,color2) + //color1: The quick access color + //color2: The main bar color + + &.sk-spinner div, + .board-backgrounds-list &.background-box, + .board-list & a + background: linear-gradient(180deg, color1 0%, color2 100%) + //background: linear-gradient(180deg, rgb(73, 155, 234) 0%, rgb(0, 174, 204) 100%) + + .is-selected .minicard + border-left: 3px solid color1 + + &.pop-over .pop-over-list li a:not(.disabled):hover, + .sidebar .sidebar-content .sidebar-btn:hover, + .sidebar-list li a:hover + background-color: lighten(color1, 10%) + + &#header ul li.current, &#header-quick-access ul li.current + border-bottom: 4px solid lighten(color2, 10%) + + &#header-quick-access + background: darken(color1, 10%) + //background: rgba(66,137,204,1) + color: #FFF + + &#header-quick-access #header-new-board-icon, + &#header-quick-access #header-user-bar, + &#header-quick-access ul li + color: rgba(255,255,255,0.5) + + // The background-color value here is not seen, + // its covered by the background of #header-main-bar + // it's just to aid transitions between boards + &#header + background-color: color2 + border-bottom: 1px solid darken(color2, 20%) + border-top: 1px solid darken(color2, 40%) + + // Since the theme uses a gradient for the header + // and gradients break transitions, it has to be set here + &#header #header-main-bar + background: linear-gradient(180deg, color1 0%, color2 100%) + + &#header #header-main-bar p + margin-bottom: 6px + + &#header #header-main-bar .board-header-btn.emphasis + background: lighten(color2, 10%) + + &:hover, + .board-header-btn-close + background: rgba(0,0,0,0.2) + + &:hover .board-header-btn-close + background: rgba(0,0,0,0.2) + + .materialCheckBox.is-checked + border-bottom: 2px solid color1 + border-right: 2px solid color1 + + .is-multiselection-active .multi-selection-checkbox + &.is-checked + .minicard + background: lighten(color2, 90%) + + &:not(.is-checked) + .minicard:hover:not(.minicard-composer) + background: lighten(color2, 97%) + + .toggle-switch:checked ~ .toggle-label + background-color: lighten(color1, 20%) + + &:after + background-color: darken(color1, 20%) + + .board-canvas + background: linear-gradient(135deg, color1 0%, color2 100%) + + .swimlane + background: none + + .list:first-child + margin-left: 15px + + .list + background: rgba(255,255,255,0.35) + margin: 10px + border: 0 + border-radius: 14px + + .list.list-composer + background: rgba(255,255,255,0.1) + height: min-content + flex: unset + width: 270px + padding-bottom: 16px + + .list.list-composer .open-list-composer + border-radius: 7px + color: rgba(0,0,0,0.3) + padding: 7px 10px + display: block + + .list.list-composer .open-list-composer:hover + box-shadow: 0 1px 2px rgba(0,0,0,.2) + background: rgba(255,255,255,0.7) + color: rgba(0,0,0,0.6) + + .list-header + background-color: rgba(255,255,255,0.25) + border-radius: 14px 14px 0 0 + + .list-header:not([class*="list-header-"]) + border-bottom: 6px solid rgba(255,255,255,0) + + .list-header .list-header-name + color: rgba(0,0,0,0.6) + + .list-body + padding: 11px + + .minicard + border-radius: 7px + padding: 10px 10px 4px 10px + box-shadow: 2px 2px 4px 0px rgba(0,0,0,0.15) + color: #222 + + .card-details + border-radius: 0 0 14px 14px + box-shadow: 0 0 7px 0 rgba(0,0,0,0.5) + margin-left: -10px + + .list-body .open-minicard-composer + border-radius: 7px + color: rgba(0,0,0,.3) + margin-bottom: 11px + + .list-body .open-minicard-composer:hover + background: rgba(255,255,255,0.7) + color: rgba(0,0,0,0.6) + + button[type=submit].primary, input[type=submit].primary + box-shadow: none + background-color: rgba(255,255,255,0.5) + color: rgba(0,0,0,0.55) + border-radius: 7px + border: 0 + + button[type="submit"].primary:hover, input[type="submit"].primary:hover + background-color: rgba(255,255,255,0.7) + color: rgba(0,0,0,0.8) + box-shadow: 0 1px 2px rgba(0,0,0,.2) + + .quiet, .quiet a + color: rgba(0,0,0,0.4) + + .list-header .list-header-watch-icon + color: rgba(0,0,0,0.5) + position: absolute + margin-top: -34px + margin-let: -11px + + a.fa, a i.fa + color: rgba(0,0,0,0.3) + + a:not(.disabled).is-active.fa, a:not(.disabled).is-active i.fa, a:not(.disabled):hover.fa, a:not(.disabled):hover i.fa + color: rgba(0,0,0,0.6) + + input[type="email"], input[type="password"], input[type="text"] + border: 0 + border-radius: 7px + + .sidebar-shadow + box-shadow: none + border-left: 9px solid color2 + + .is-open .sidebar-shadow + box-shadow: -10px 0 8px rgba(0,0,0,0.3) + + .list.ui-sortable-helper + transform:rotate(0deg) + + .minicard-wrapper.placeholder + background: rgba(0,0,0,0.1) + + .minicard-wrapper.ui-sortable-helper + transform:rotate(0deg) + opacity: 0.8 + + .list-body .open-minicard-composer + color: rgba(0,0,0,.3) + + .swinlane.ui-sortable-helper + transform:rotate(0deg) + + .swimlane .swimlane-header-wrap + background: linear-gradient(0deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.25) 100%) + + .swimlane-header-wrap .inlined-form + width: 100% + + .swimlane-header-wrap .list-composer + text-align: center + margin: 5px + + .swimlane-header-wrap .list-name-input.full-line + margin: 0 + display: inline-block + width: 270px + + .swimlane-header-wrap .edit-controls + display: inline-block + vertical-align: middle + + .swimlane-header-wrap .primary.confirm + margin-right: 0 + + .swimlane-header-wrap .fa.fa-times-thin + margin-top: 2px + + // This is a general fix so that the little grabby hand appears when dragging the list via the title + .list.ui-sortable-helper, + .list.ui-sortable-helper .list-header.ui-sortable-handle, + .list.ui-sortable-helper .viewer + cursor:-webkit-grabbing; + cursor:grabbing + +.board-color-clearblue + setBoardClear(rgb(73, 155, 234),rgb(0, 174, 204)) + +/* + Alternate "Natural" Styling +*/ +.board-color-natural + setBoardColor(#596557) + + &#header-quick-access + background-color: #2d392b + + .ui-sortable + background-color:#dedede + + .list-header + background-color: #c9cfc3 + border-bottom: 6px solid #c9cfc3 + + .swimlane .swimlane-header-wrap + background-color: #c2c0ab + +/* + Alternate "Modern" Styling +*/ +.board-color-modern + setBoardColor(#2A80B8) + + /* General */ + body + background: #f5f5f5 + + &#header-quick-access + padding: 10px + font-size: 14px + background: #333 !important + + &#header-quick-access ul + overflow: visible + + &#header-quick-access ul li.current + border: 0 !important + font-weight: bold + + &#header-quick-access ul li.separator + display: none + + &#header-quick-access ul li:nth-child(3) + margin-right: 10px + + &#header-quick-access ul li a + padding: 5px 10px + border-radius: 2px + + &#header-quick-access ul li.current a + border-radius: 2px + background: rgba(255,255,255,.2) + + &#header #header-main-bar h1 + font-family: Poppins + font-weight: bold + &#header-quick-access #header-user-bar + position relative + + &#header-quick-access #header-user-bar .header-user-bar-name + margin: 5px 3px 0 0 + + section#notifications-drawer + top: 46px + box-shadow: 0 4px 20px rgba(0,0,0,.1) + max-width: 100% + + section#notifications-drawer .header + top: 46px + border-radius: 0 3px + height: 21px + background: #f7f7f7 + + .board-canvas + background: #f5f5f5 + + /* Swimlane */ + .swimlane + background: none + + .swimlane .swimlane-header-wrap .swimlane-header + font-family: Poppins + + /* All board views */ + .board-list .board-list-item + padding: 20px + + .board-list-item-name + font-family: Poppins + + /* Board */ + .list + background: transparent + border-left: 0 + margin: 10px 0 + padding: 0px + border-radius: 5px + min-width: 300px + + .list-body .open-minicard-composer:hover /*me*/ + background: none + box-shadow: none + + .list:first-child + margin-left: 5px + + .list.list-composer.js-list-composer + transition: all .3s ease + min-width: 80px + + .open-list-composer.js-open-inlined-form:hover + color: #222 + + .list-header + background: none + border-bottom-width: 0px + + .list-header .list-header-name + font-family: Poppins + color: #000 + font-weight: 500 + + /* Card changes */ + .minicard + padding: 15px 15px 10px + box-shadow: 0 3px 8px rgba(0,0,0,.05) + + .minicard-plum:hover:not(.minicard-composer), .is-selected .minicard-plum, .draggable-hover-card .minicard-plum + background: none + + .minicard-title + line-height: 1.5em + + .minicard .minicard-cover + background-size: cover + margin: -15px -15px 10px + height: 100px + + .card-label-orange + color: #fff + + .card-date + font-size: 12px + padding: 3px 5px + + /* Pop over */ + .header-title + font-family: Poppins + font-size: 16px + color: #333 + + .pop-over + box-shadow: 0 4px 20px rgba(0,0,0,.2) + border: 0 + border-radius: 5px + + .pop-over .header + padding: 10px + border-bottom: 0 + border-radius: 5px 5px 0 0 + background:#eee + + .pop-over .header .header-title + font-family: Poppins + font-size:16px + color:#333 + + .pop-over .header .close-btn + font-size:20px + top:6px + right:8px + + .pop-over .content-container .content + padding: 5px 20px 20px + width: 260px + + .pop-over-list li > a + border-radius: 5px + + .pop-over-list li > a > i + margin-right: 5px + + .pop-over-list li>a .sub-name + margin-bottom: 8px + + /* Sidebar */ + .sidebar .sidebar-shadow + box-shadow: 0 0 60px rgba(0,0,0,.2) + + .sidebar .sidebar-content + padding: 30px + + /* Notifications */ + .board-color-modern section#notifications-drawer + border-radius:5px + + .board-color-modern section#notifications-drawer .header + padding: 18px 16px + border-bottom: 0 + border-radius: 5px 5px 0 0 + background: #eee + + .board-color-modern section#notifications-drawer .header h5 + font-family: Poppins + font-weight: bold + + .board-color-modern section#notifications-drawer .header .close + font-size: 20px + top: 14px + + section#notifications-drawer .header .toggle-read + top: 18px + +/* + Alternate "Modern Dark" Styling +*/ +.board-color-moderndark + setBoardColor(#2a2a2a) + + /* General */ + body + background: #2a2a2a + + .board-wrapper .board-canvas .board-overlay + opacity: .6 + + /* Forms */ + button[type=submit].primary, .board-color-modern input[type=submit].primary + background-color: #819C5D + + .toggle-switch:checked~.toggle-label + background-color: #D2E9B4 + + .toggle-label:after, .board-color-modern .toggle-switch:checked~.toggle-label:after + background-color: #819C5D !important + + button, input:not([type=file]), select, textarea + border-radius: 2px + + /* Headers */ + &#header + background-color: #262626 + border-bottom: 1px solid #555555; + border-top: 1px solid #555555; + + &#header-quick-access, .background-box, #header + background-color: #333333 + + &#header-quick-access + padding: 4px + font-size: 14px + + &#header-quick-access .allBoards + padding: 5px 10px 0 10px; + + &#header-quick-access ul.header-quick-access-list + margin: -5px 0 -5px 0 + + &#header #header-main-bar + padding-top: 3px + padding-bottom: 3px + + &#header-quick-access ul + overflow: visible + + &#header-quick-access ul li.current + border: 0 !important + font-weight: bold + + &#header-quick-access ul li.separator + display: none + + &#header-quick-access ul li:nth-child(3) + margin-right: 10px + + &#header-quick-access ul li a + padding: 5px 10px + border-radius: 2px + + &#header-quick-access ul li.current a + border-radius: 2px + background: rgba(255,255,255,.2) + + &#header #header-main-bar h1 + font-family: Poppins + font-weight: bold + line-height: 0.8em + padding-top: 10px + + /* Content */ + .board-canvas + background: #2a2a2a + + /* Swimlanes */ + .swimlane .swimlane-header-wrap + background-color: #494949 + color: #cccccc + padding: 4px 0 + + .swimlane .swimlane-header-wrap .swimlane-header + font-family: Poppins + + .swimlane .swimlane-header-wrap .swimlane-header-menu + padding: 6px + font-size: 16px + + .swimlane .swimlane-header-wrap .swimlane-header-plus-icon + font-size: 16px + + .swimlane + background: #2a2a2a + line-height: 15px + max-height: 100% + + /* Lists */ + .swimlane .list + background: #666666 + border-radius: 0 + border: 0px solid #666666 + flex: 0 0 265px; + + .swimlane .list:first-child + margin-left: 0 + + .swimlane .list:nth-child(even) + background: #5f5f5f + + .swimlane .list:nth-child(odd) .list-header + background: #3b3b3b + + .list-header + background: #333333 + padding: 10px + border-bottom: 0 + + .list-header .viewer + padding-left: 10px + + .list-header .list-header-name + line-height: 14px + color: #eeeeee + + .list-header .list-header-menu + padding: 10px + top: 0 + + .list-header .list-header-plus-icon + color: #a6a6a6 + + .list-body + scrollbar-width: thin + scrollbar-color: #343434 #999999 + + .list-body::-webkit-scrollbar + width: 10px + + .list-body::-webkit-scrollbar-track + background: #343434 + border-radius: 3px + margin: 4px 0 + + .list-body::-webkit-scrollbar-thumb + background-color: #999999 + border-radius: 6px + border: 3px solid #343434 + + .list-body .open-minicard-composer:hover + background: none + box-shadow: none + border-bottom: 0 + + .list-body a.open-minicard-composer, .list-body a.open-minicard-composer i, .list .list-composer .open-list-composer i + color: #bbbbbb + + .list-body a.open-minicard-composer:hover, .list-body a.open-minicard-composer:hover i, .list .list-composer .open-list-composer:hover i + color: #ffffff + + /* Mini Card */ + .minicard-wrapper + margin-bottom: 12px + + .minicard + background-color: #444444 + color: #cccccc + border-radius: 2px + font-size: 0.95em + padding: 10px + box-shadow: 0 4px 3px -3px rgba(0,0,0,0.8) + border-bottom: 1px solid #666666 + + .minicard:hover + background-color: #494949 !important + + .minicard .minicard-labels + margin-bottom: 4px + + .minicard .card-label + font-size: 11px + font-weight: 400 + padding: 1px 6px 0 + border-radius: 2px + + .minicard .badges + color: #bbbbbb + + .minicard .date + margin-top: 10px + font-size: 11px + + .card-date + color: #444444 + border-radius: 2px + + .card-date.almost-due + color: #444444 + + .minicard.minicard-composer textarea.minicard-composer-textarea:focus + background-color: #eeeeee + color: #333333 + padding: 6px + + .is-selected .minicard + background-color: #666666 + + /* Card Details */ + .card-details + position: absolute + top: 30px + left: calc(50% - 384px) + width: 768px + max-height: calc(100% - 60px) + background-color: #454545 + color: #cccccc + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19) + border: 1px solid #111111 + z-index: 100 !important + + .card-details + scrollbar-width: thin + scrollbar-color: #343434 #999999 + + .card-details::-webkit-scrollbar + width: 16px + + .card-details::-webkit-scrollbar-track + background: #343434 + + .card-details::-webkit-scrollbar-thumb + background-color: #999999 + border-radius: 6px + border: 4px solid #343434 + + .card-details .card-details-header + background: #333333 + color: #cccccc + border-bottom: 2px solid #2d2d2d + + .card-details hr + background: #2d2d2d + + .card-details .card-details-item-title + color: #ffffff + + .card-details .new-description textarea, .card-details .new-comment textarea + background-color: #dddddd + color: #111111 + + .card-details .checklist + background-color: transparent + margin-bottom: 10px + + .card-details .checklist-item + background-color: rgba(255,255,255,0.1) + padding: 4px 8px + border-radius: 2px + font-size: 13px + margin-top: 5px + + .card-details .checklist-item:hover + background-color: rgba(255,255,255,0.2) + + .card-details .checklist-item .item-title .viewer p + max-width: auto + + .card-details .check-box.materialCheckBox + border-color: #ffffff + + .card-details .check-box.materialCheckBox.is-checked + border-bottom: 2px solid #819C5D + border-right: 2px solid #819C5D + border-top: 0 + border-left: 0 + + .card-details .js-add-checklist-item + margin-top: 4px + + .checklist-items .add-checklist-item + margin-top: .7em + + .card-details .activities .activity .activity-desc .activity-comment + background-color: #cccccc + color: #222222 + + /* Sidebar */ + .sidebar .sidebar-shadow + background-color: #222222 + box-shadow: -10px 0 5px -10px #444444 + border-left: 1px solid #333333 + color: #cccccc + + .activities .activity .activity-desc .activity-comment + background-color: #cccccc + color: #222222 + + /* Pop-Ups for "Modern Dark" */ +.pop-over.board-color-moderndark + background-color: #454545 + color: #cccccc + border: 1px solid #111111 + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19) + +.pop-over.board-color-moderndark .header + background-color: #333333 + +.pop-over.board-color-moderndark .header-title + font-family: Poppins + font-size: 16px + color: #cccccc + +.pop-over.board-color-moderndark .pop-over-list li:hover > a + background-color: #819C5D !important diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 4c0edac49..019f3deba 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -1,7 +1,9 @@ template(name="boardHeaderBar") h1.header-board-menu with currentBoard - a(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}") + if $eq title 'Templates' + | {{_ 'templates'}} + else +viewer = title @@ -9,6 +11,10 @@ template(name="boardHeaderBar") unless isMiniScreen if currentBoard if currentUser + with currentBoard + a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) + i.fa.fa-pencil-square-o + a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") @@ -31,6 +37,12 @@ template(name="boardHeaderBar") if $eq watchLevel "muted" i.fa.fa-bell-slash span {{_ watchLevel}} + a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}") + i.fa.fa-sort + span {{#if isSortActive }}{{_ 'Sort is on'}}{{else}}{{_ 'sort-cards'}}{{/if}} + if isSortActive + a.board-header-btn-close.js-sort-reset(title="Remove Sort") + i.fa.fa-times-thin else a.board-header-btn.js-log-in( @@ -42,6 +54,10 @@ template(name="boardHeaderBar") if currentBoard if isMiniScreen if currentUser + with currentBoard + a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) + i.fa.fa-pencil-square-o + a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") @@ -99,13 +115,13 @@ template(name="boardHeaderBar") a.board-header-btn.js-toggle-board-view( title="{{_ 'board-view'}}") i.fa.fa-caret-down - if $eq boardView 'board-view-lists' - i.fa.fa-trello if $eq boardView 'board-view-swimlanes' i.fa.fa-th-large + if $eq boardView 'board-view-lists' + i.fa.fa-trello if $eq boardView 'board-view-cal' i.fa.fa-calendar - span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}} + span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-swimlanes'}}{{/if}} if canModifyBoard a.board-header-btn.js-multiselection-activate( @@ -118,7 +134,7 @@ template(name="boardHeaderBar") i.fa.fa-times-thin .separator - a.board-header-btn.js-toggle-sidebar + a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}") i.fa.fa-navicon template(name="boardVisibilityList") @@ -172,13 +188,6 @@ template(name="boardChangeWatchPopup") template(name="boardChangeViewPopup") ul.pop-over-list - li - with "board-view-lists" - a.js-open-lists-view - i.fa.fa-trello.colorful - | {{_ 'board-view-lists'}} - if $eq Utils.boardView "board-view-lists" - i.fa.fa-check li with "board-view-swimlanes" a.js-open-swimlanes-view @@ -186,6 +195,13 @@ template(name="boardChangeViewPopup") | {{_ 'board-view-swimlanes'}} if $eq Utils.boardView "board-view-swimlanes" i.fa.fa-check + li + with "board-view-lists" + a.js-open-lists-view + i.fa.fa-trello.colorful + | {{_ 'board-view-lists'}} + if $eq Utils.boardView "board-view-lists" + i.fa.fa-check li with "board-view-cal" a.js-open-cal-view @@ -212,6 +228,9 @@ template(name="createBoard") = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. + //a.flex.js-toggle-add-template-container + // .materialCheckBox#add-template-container + // span {{_ 'add-template-container'}} input.primary.wide(type="submit" value="{{_ 'create'}}") span.quiet | {{_ 'or'}} @@ -247,3 +266,19 @@ template(name="boardChangeTitlePopup") template(name="boardCreateRulePopup") p {{_ 'close-board-pop'}} button.js-confirm.negate.full(type="submit") {{_ 'archive'}} + + +template(name="cardsSortPopup") + ul.pop-over-list + li + a.js-sort-due {{_ 'due-date'}} + hr + li + a.js-sort-title {{_ 'title-alphabetically'}} + hr + li + a.js-sort-created-desc {{_ 'created-at-newest-first'}} + hr + li + a.js-sort-created-asc {{_ 'created-at-oldest-first'}} + diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index dc553134f..05415222f 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -2,6 +2,7 @@ const DOWNCLS = 'fa-sort-down'; const UPCLS = 'fa-sort-up'; */ +const sortCardsBy = new ReactiveVar(''); Template.boardMenuPopup.events({ 'click .js-rename-board': Popup.open('boardChangeTitle'), 'click .js-custom-fields'() { @@ -33,22 +34,6 @@ Template.boardMenuPopup.events({ 'click .js-card-settings': Popup.open('boardCardSettings'), }); -Template.boardMenuPopup.helpers({ - exportUrl() { - const params = { - boardId: Session.get('currentBoard'), - }; - const queryParams = { - authToken: Accounts._storedLoginToken(), - }; - return FlowRouter.path('/api/boards/:boardId/export', params, queryParams); - }, - exportFilename() { - const boardId = Session.get('currentBoard'); - return `wekan-export-board-${boardId}.json`; - }, -}); - Template.boardChangeTitlePopup.events({ submit(event, templateInstance) { const newTitle = templateInstance @@ -126,6 +111,7 @@ BlazeComponent.extendComponent({ 'click .js-open-filter-view'() { Sidebar.setView('filter'); }, + 'click .js-sort-cards': Popup.open('cardsSort'), /* 'click .js-open-sort-view'(evt) { const target = evt.target; @@ -143,6 +129,9 @@ BlazeComponent.extendComponent({ Sidebar.setView(); Filter.reset(); }, + 'click .js-sort-reset'() { + Session.set('sortBy', ''); + }, 'click .js-open-search-view'() { Sidebar.setView('search'); }, @@ -176,6 +165,9 @@ Template.boardHeaderBar.helpers({ boardView() { return Utils.boardView(); }, + isSortActive() { + return Session.get('sortBy') ? true : false; + }, }); Template.boardChangeViewPopup.events({ @@ -217,24 +209,79 @@ const CreateBoard = BlazeComponent.extendComponent({ this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get()); }, + toggleAddTemplateContainer() { + $('#add-template-container').toggleClass('is-checked'); + }, + onSubmit(event) { event.preventDefault(); const title = this.find('.js-new-board-title').value; - const visibility = this.visibility.get(); - this.boardId.set( - Boards.insert({ - title, - permission: visibility, - }), - ); + const addTemplateContainer = $('#add-template-container.is-checked').length > 0; + if (addTemplateContainer) { + //const templateContainerId = Meteor.call('setCreateTemplateContainer'); + //Utils.goBoardId(templateContainerId); + //alert('niinku template ' + Meteor.call('setCreateTemplateContainer')); - Swimlanes.insert({ - title: 'Default', - boardId: this.boardId.get(), - }); + this.boardId.set( + Boards.insert({ + // title: TAPi18n.__('templates'), + title: title, + permission: 'private', + type: 'template-container', + }), + ); - Utils.goBoardId(this.boardId.get()); + // Insert the card templates swimlane + Swimlanes.insert({ + // title: TAPi18n.__('card-templates-swimlane'), + title: 'Card Templates', + boardId: this.boardId.get(), + sort: 1, + type: 'template-container', + }), + + // Insert the list templates swimlane + Swimlanes.insert( + { + // title: TAPi18n.__('list-templates-swimlane'), + title: 'List Templates', + boardId: this.boardId.get(), + sort: 2, + type: 'template-container', + }, + ); + + // Insert the board templates swimlane + Swimlanes.insert( + { + //title: TAPi18n.__('board-templates-swimlane'), + title: 'Board Templates', + boardId: this.boardId.get(), + sort: 3, + type: 'template-container', + }, + ); + + Utils.goBoardId(this.boardId.get()); + + } else { + const visibility = this.visibility.get(); + + this.boardId.set( + Boards.insert({ + title, + permission: visibility, + }), + ); + + Swimlanes.insert({ + title: 'Default', + boardId: this.boardId.get(), + }); + + Utils.goBoardId(this.boardId.get()); + } }, events() { @@ -248,6 +295,7 @@ const CreateBoard = BlazeComponent.extendComponent({ submit: this.onSubmit, 'click .js-import-board': Popup.open('chooseBoardSource'), 'click .js-board-template': Popup.open('searchElement'), + 'click .js-toggle-add-template-container': this.toggleAddTemplateContainer, }, ]; }, @@ -384,3 +432,44 @@ BlazeComponent.extendComponent({ }, }).register('listsortPopup'); */ + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-sort-due'() { + const sortBy = { + dueAt: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('due-date')); + Popup.close(); + }, + 'click .js-sort-title'() { + const sortBy = { + title: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('title')); + Popup.close(); + }, + 'click .js-sort-created-asc'() { + const sortBy = { + createdAt: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('date-created-newest-first')); + Popup.close(); + }, + 'click .js-sort-created-desc'() { + const sortBy = { + createdAt: -1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('date-created-oldest-first')); + Popup.close(); + }, + }, + ]; + }, +}).register('cardsSortPopup'); diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index 79bae5020..dae1221dc 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -1,10 +1,11 @@ template(name="boardList") .wrapper - ul.board-list.clearfix + ul.board-list.clearfix.js-boards li.js-add-board - a.board-list-item.label {{_ 'add-board'}} + a.board-list-item.label(title="{{_ 'add-board'}}") + | {{_ 'add-board'}} each boards - li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) + li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board if isInvited .board-list-item span.details @@ -16,50 +17,97 @@ template(name="boardList") button.js-accept-invite.primary {{_ 'accept'}} button.js-decline-invite {{_ 'decline'}} else - a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name - +viewer - = title - i.fa.js-star-board( - class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" - title="{{_ 'star-board-title'}}") - p.board-list-item-desc - +viewer - = description - if hasSpentTimeCards - i.fa.js-has-spenttime-cards( - class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" - title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - unless isMiniScreen - if isSandstorm - i.fa.js-clone-board( - class="fa-clone" - title="{{_ 'duplicate-board'}}") - i.fa.js-archive-board( - class="fa-archive" - title="{{_ 'archive-board'}}") - else if currentUser.isBoardAdmin - i.fa.js-clone-board( - class="fa-clone" - title="{{_ 'duplicate-board'}}") - i.fa.js-archive-board( - class="fa-archive" - title="{{_ 'archive-board'}}") - else if currentUser.isAdmin - i.fa.js-clone-board( - class="fa-clone" - title="{{_ 'duplicate-board'}}") - i.fa.js-archive-board( - class="fa-archive" - title="{{_ 'archive-board'}}") + if $eq type "template-container" + a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'template-container'}}") + +viewer + = title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + if isMiniScreen + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'Drag board'}}") + unless isMiniScreen + if isSandstorm + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if isAdministrable + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if currentUser.isAdmin + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else + a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}") + +viewer + = title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + if isMiniScreen + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'Drag board'}}") + unless isMiniScreen + if isSandstorm + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if isAdministrable + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if currentUser.isAdmin + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") template(name="boardListHeaderBar") - h1 {{_ 'my-boards'}} - .board-header-btns.right - a.board-header-btn.js-open-archived-board - i.fa.fa-archive - span {{_ 'archives'}} - a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") - i.fa.fa-clone - span {{_ 'templates'}} + h1 {{_ title }} + //.board-header-btns.right + // a.board-header-btn.js-open-archived-board + // i.fa.fa-archive + // span {{_ 'archives'}} + // a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") + // i.fa.fa-clone + // span {{_ 'templates'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 3918af829..0e0815b05 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,4 +1,5 @@ const subManager = new SubsManager(); +const { calculateIndex, enableClickOnTouch } = Utils; Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -7,6 +8,9 @@ Template.boardListHeaderBar.events({ }); Template.boardListHeaderBar.helpers({ + title() { + return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public'; + }, templatesBoardId() { return Meteor.user() && Meteor.user().getTemplatesBoardId(); }, @@ -18,22 +22,91 @@ Template.boardListHeaderBar.helpers({ BlazeComponent.extendComponent({ onCreated() { Meteor.subscribe('setting'); + let currUser = Meteor.user(); + let userLanguage; + if(currUser && currUser.profile){ + userLanguage = currUser.profile.language + } + if (userLanguage) { + TAPi18n.setLanguage(userLanguage); + T9n.setLanguage(userLanguage); + } + }, + + onRendered() { + const itemsSelector = '.js-board:not(.placeholder)'; + + const $boards = this.$('.js-boards'); + $boards.sortable({ + connectWith: '.js-boards', + tolerance: 'pointer', + appendTo: '.board-list', + helper: 'clone', + distance: 7, + items: itemsSelector, + placeholder: 'board-wrapper placeholder', + start(evt, ui) { + ui.helper.css('z-index', 1000); + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + const prevBoardDom = ui.item.prev('.js-board').get(0); + const nextBoardBom = ui.item.next('.js-board').get(0); + const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1); + + const boardDomElement = ui.item.get(0); + const board = Blaze.getData(boardDomElement); + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $boards.sortable('cancel'); + + board.move(sortIndex.base); + }, + }); + + // ugly touch event hotfix + enableClickOnTouch(itemsSelector); + + // Disable drag-dropping if the current user is not a board member or is comment only + this.autorun(() => { + if (Utils.isMiniScreen()) { + $boards.sortable({ + handle: '.board-handle', + }); + } + }); }, boards() { - return Boards.find( - { - archived: false, - 'members.userId': Meteor.userId(), - type: 'board', - }, - { sort: ['title'] }, - ); + const query = { + archived: false, + //type: { $in: ['board','template-container'] }, + type: 'board', + }; + if (FlowRouter.getRouteName() === 'home') + query['members.userId'] = Meteor.userId(); + else query.permission = 'public'; + + return Boards.find(query, { + sort: { sort: 1 /* boards default sorting */ }, + }); }, isStarred() { const user = Meteor.user(); return user && user.hasStarred(this.currentData()._id); }, + isAdministrable() { + const user = Meteor.user(); + return user && user.isBoardAdmin(this.currentData()._id); + }, hasOvertimeCards() { subManager.subscribe('board', this.currentData()._id, false); @@ -61,9 +134,13 @@ BlazeComponent.extendComponent({ }, 'click .js-clone-board'(evt) { Meteor.call( - 'cloneBoard', + 'copyBoard', this.currentData()._id, - Session.get('fromBoard'), + { + sort: Boards.find({ archived: false }).count(), + type: 'board', + title: Boards.findOne(this.currentData()._id).title, + }, (err, res) => { if (err) { this.setError(err.error); diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl index ae366e833..635138f3d 100644 --- a/client/components/boards/boardsList.styl +++ b/client/components/boards/boardsList.styl @@ -7,10 +7,23 @@ $spaceBetweenTiles = 16px li float: left - width: 25% + width: 20% box-sizing: border-box position: relative + &.placeholder:after + content: ''; + display: block; + background: darken(white, 20%) + border-radius: 3px; + height: 106px; + margin: 8px; + + &.ui-sortable-helper + cursor: grabbing + transform: rotate(4deg) + display: block !important + &.starred .fa-star, .fa-star-o @@ -20,17 +33,20 @@ $spaceBetweenTiles = 16px overflow: hidden; background-color: #999 color: #f6f6f6 - height: 90px + min-height: 100px font-size: 16px line-height: 22px border-radius: 3px display: block font-weight: 700 - min-height: 18px padding: 8px margin: ($spaceBetweenTiles/2) position: relative text-decoration: none + word-wrap: break-word + + &.template-container + border: 4px solid #fff &.tile background-size: auto @@ -55,7 +71,7 @@ $spaceBetweenTiles = 16px .label font-weight: normal - line-height:90px + line-height: 56px :hover background-color:#939393 @@ -183,7 +199,7 @@ $spaceBetweenTiles = 16px overflow: scroll li - width: 50% + width: 50% .board-list-item overflow: hidden @@ -194,6 +210,22 @@ $spaceBetweenTiles = 16px top: -100px left: -100px + .board-handle + position: absolute + padding: 7px + top: 50% + transform: translateY(-50%) + right: 10px + font-size: 24px + @media screen and (max-width: 360px) li width: 100% + + .board-handle + position: absolute + padding: 7px + top: 50% + transform: translateY(-50%) + right: 10px + font-size: 24px diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade index 61454fa79..ee714aefa 100644 --- a/client/components/cards/attachments.jade +++ b/client/components/cards/attachments.jade @@ -46,14 +46,14 @@ template(name="attachmentsGalery") | {{_ 'remove-cover'}} else | {{_ 'add-cover'}} - a.js-confirm-delete - i.fa.fa-close - | {{_ 'delete'}} + if currentUser.isBoardAdmin + a.js-confirm-delete + i.fa.fa-close + | {{_ 'delete'}} if currentUser.isBoardMember unless currentUser.isCommentOnly unless currentUser.isWorker //li.attachment-item.add-attachment - a.js-add-attachment + a.js-add-attachment(title="{{_ 'add-attachment' }}") i.fa.fa-plus - | {{_ 'add-attachment' }} diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index e4439155e..bd668a8e9 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -45,6 +45,12 @@ Template.attachmentsGalery.events({ }, }); +Template.attachmentsGalery.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, +}); + Template.previewAttachedImagePopup.events({ 'click .js-large-image-clicked'() { Popup.close(); diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade index 0a4d26e5b..8acf9f5e9 100644 --- a/client/components/cards/cardCustomFields.jade +++ b/client/components/cards/cardCustomFields.jade @@ -4,8 +4,7 @@ template(name="cardCustomFieldsPopup") li.item(class="") a.name.js-select-field(href="#") span.full-name - +viewer - = name + = name if hasCustomField i.fa.fa-check hr @@ -53,6 +52,31 @@ template(name="cardCustomField-number") if value = value +template(name="cardCustomField-checkbox") + .js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}") + if canModifyCard + .check-box-container + .check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}") + else + .materialCheckBox(class="{{#if data.value }}is-checked{{/if}}") + +template(name="cardCustomField-currency") + if canModifyCard + +inlinedForm(classNames="js-card-customfield-currency") + input(type="text" value=data.value) + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if value + = formattedValue + else + | {{_ 'edit'}} + else + if value + = formattedValue + template(name="cardCustomField-date") if canModifyCard a.js-edit-date(title="{{showTitle}}" class="{{classes}}") @@ -95,3 +119,24 @@ template(name="cardCustomField-dropdown") if value +viewer = selectedItem + +template(name="cardCustomField-stringtemplate") + if canModifyCard + +inlinedForm(classNames="js-card-customfield-stringtemplate") + each item in stringtemplateItems.get + input.js-card-customfield-stringtemplate-item(type="text" value=item placeholder="") + input.js-card-customfield-stringtemplate-item.last(type="text" value="" placeholder="{{_ 'custom-field-stringtemplate-item-placeholder'}}" autofocus) + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if value + +viewer + = formattedValue + else + | {{_ 'edit'}} + else + if value + +viewer + = formattedValue diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index d0b9c72da..fee41c00b 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -1,3 +1,6 @@ +import { DatePicker } from '/client/lib/datepicker'; +import Cards from '/models/cards'; + Template.cardCustomFieldsPopup.helpers({ hasCustomField() { const card = Cards.findOne(Session.get('currentCard')); @@ -80,6 +83,56 @@ CardCustomField.register('cardCustomField'); } }.register('cardCustomField-number')); +// cardCustomField-checkbox +(class extends CardCustomField { + onCreated() { + super.onCreated(); + } + + toggleItem() { + this.card.setCustomField(this.customFieldId, !this.data().value); + } + + events() { + return [ + { + 'click .js-checklist-item .check-box-container': this.toggleItem, + }, + ]; + } +}.register('cardCustomField-checkbox')); + +// cardCustomField-currency +(class extends CardCustomField { + onCreated() { + super.onCreated(); + + this.currencyCode = this.data().definition.settings.currencyCode; + } + + formattedValue() { + const locale = TAPi18n.getLanguage(); + + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: this.currencyCode, + }).format(this.data().value); + } + + events() { + return [ + { + 'submit .js-card-customfield-currency'(event) { + event.preventDefault(); + // To allow input separated by comma, the comma is replaced by a period. + const value = Number(this.find('input').value.replace(/,/i, '.'), 10); + this.card.setCustomField(this.customFieldId, value); + }, + }, + ]; + } +}.register('cardCustomField-currency')); + // cardCustomField-date (class extends CardCustomField { onCreated() { @@ -184,3 +237,90 @@ CardCustomField.register('cardCustomField'); ]; } }.register('cardCustomField-dropdown')); + +// cardCustomField-stringtemplate +(class extends CardCustomField { + onCreated() { + super.onCreated(); + + this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat; + this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator; + + this.stringtemplateItems = new ReactiveVar(this.data().value ?? []); + } + + formattedValue() { + return (this.data().value ?? []) + .filter(value => !!value.trim()) + .map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value)) + .join(this.stringtemplateSeparator ?? ''); + } + + getItems() { + return Array.from(this.findAll('input')) + .map(input => input.value) + .filter(value => !!value.trim()); + } + + events() { + return [ + { + 'submit .js-card-customfield-stringtemplate'(event) { + event.preventDefault(); + const items = this.getItems(); + this.card.setCustomField(this.customFieldId, items); + }, + + 'keydown .js-card-customfield-stringtemplate-item'(event) { + if (event.keyCode === 13) { + event.preventDefault(); + + if (event.metaKey || event.ctrlKey) { + this.find('button[type=submit]').click(); + } else if (event.target.value.trim()) { + const inputLast = this.find('input.last'); + + let items = this.getItems(); + + if (event.target === inputLast) { + inputLast.value = ''; + } else if (event.target.nextSibling === inputLast) { + inputLast.focus(); + } else { + event.target.blur(); + + const idx = Array.from(this.findAll('input')).indexOf( + event.target, + ); + items.splice(idx + 1, 0, ''); + + Tracker.afterFlush(() => { + const element = this.findAll('input')[idx + 1]; + element.focus(); + element.value = ''; + }); + } + + this.stringtemplateItems.set(items); + } + } + }, + + 'blur .js-card-customfield-stringtemplate-item'(event) { + if ( + !event.target.value.trim() || + event.target === this.find('input.last') + ) { + const items = this.getItems(); + this.stringtemplateItems.set(items); + this.find('input.last').value = ''; + } + }, + + 'click .js-close-inlined-form'(event) { + this.stringtemplateItems.set(this.data().value ?? []); + }, + }, + ]; + } +}.register('cardCustomField-stringtemplate')); diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade index 2e4475067..9bcc67f2f 100644 --- a/client/components/cards/cardDate.jade +++ b/client/components/cards/cardDate.jade @@ -8,3 +8,7 @@ template(name="dateBadge") time(datetime="{{showISODate}}") | {{showDate}} +template(name="dateCustomField") + a(title="{{showTitle}}" class="{{classes}}") + time(datetime="{{showISODate}}") + | {{showDate}} diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index c4b5c6d8a..6bd46d5a5 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -1,96 +1,4 @@ -// Edit received, start, due & end dates -BlazeComponent.extendComponent({ - template() { - return 'editCardDate'; - }, - - onCreated() { - this.error = new ReactiveVar(''); - this.card = this.data(); - this.date = new ReactiveVar(moment.invalid()); - }, - - onRendered() { - const $picker = this.$('.js-datepicker') - .datepicker({ - todayHighlight: true, - todayBtn: 'linked', - language: TAPi18n.getLanguage(), - }) - .on( - 'changeDate', - function(evt) { - this.find('#date').value = moment(evt.date).format('L'); - this.error.set(''); - this.find('#time').focus(); - }.bind(this), - ); - - if (this.date.get().isValid()) { - $picker.datepicker('update', this.date.get().toDate()); - } - }, - - showDate() { - if (this.date.get().isValid()) return this.date.get().format('L'); - return ''; - }, - showTime() { - if (this.date.get().isValid()) return this.date.get().format('LT'); - return ''; - }, - dateFormat() { - return moment.localeData().longDateFormat('L'); - }, - timeFormat() { - return moment.localeData().longDateFormat('LT'); - }, - - events() { - return [ - { - 'keyup .js-date-field'() { - // parse for localized date format in strict mode - const dateMoment = moment(this.find('#date').value, 'L', true); - if (dateMoment.isValid()) { - this.error.set(''); - this.$('.js-datepicker').datepicker('update', dateMoment.toDate()); - } - }, - 'keyup .js-time-field'() { - // parse for localized time format in strict mode - const dateMoment = moment(this.find('#time').value, 'LT', true); - if (dateMoment.isValid()) { - this.error.set(''); - } - }, - 'submit .edit-date'(evt) { - evt.preventDefault(); - - // if no time was given, init with 12:00 - const time = - evt.target.time.value || - moment(new Date().setHours(12, 0, 0)).format('LT'); - - const dateString = `${evt.target.date.value} ${time}`; - const newDate = moment(dateString, 'L LT', true); - if (newDate.isValid()) { - this._storeDate(newDate.toDate()); - Popup.close(); - } else { - this.error.set('invalid-date'); - evt.target.date.focus(); - } - }, - 'click .js-delete-date'(evt) { - evt.preventDefault(); - this._deleteDate(); - Popup.close(); - }, - }, - ]; - }, -}); +import { DatePicker } from '/client/lib/datepicker'; Template.dateBadge.helpers({ canModifyCard() { @@ -279,7 +187,7 @@ class CardStartDate extends CardDate { // if dueAt or endAt exist & are > startAt, startAt doesn't need to be flagged if ((endAt && theDate.isAfter(endAt)) || (dueAt && theDate.isAfter(dueAt))) classes += 'long-overdue'; - else if (theDate.isBefore(now, 'minute')) classes += 'almost-due'; + else if (theDate.isAfter(now)) classes += ''; else classes += 'current'; return classes; } @@ -363,6 +271,33 @@ class CardEndDate extends CardDate { } CardEndDate.register('cardEndDate'); +class CardCustomFieldDate extends CardDate { + template() { + return 'dateCustomField'; + } + + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(moment(self.data().value)); + }); + } + + classes() { + return 'customfield-date'; + } + + showTitle() { + return ''; + } + + events() { + return []; + } +} +CardCustomFieldDate.register('cardCustomFieldDate'); + (class extends CardReceivedDate { showDate() { return this.date.get().format('l'); @@ -386,3 +321,63 @@ CardEndDate.register('cardEndDate'); return this.date.get().format('l'); } }.register('minicardEndDate')); + +(class extends CardCustomFieldDate { + showDate() { + return this.date.get().format('l'); + } +}.register('minicardCustomFieldDate')); + +class VoteEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(moment(self.data().getVoteEnd())); + }); + } + classes() { + const classes = 'end-date' + ' '; + return classes; + } + showDate() { + return this.date.get().format('l LT'); + } + showTitle() { + return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + } + + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editVoteEndDate'), + }); + } +} +VoteEndDate.register('voteEndDate'); + +class PokerEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(moment(self.data().getPokerEnd())); + }); + } + classes() { + const classes = 'end-date' + ' '; + return classes; + } + showDate() { + return this.date.get().format('l LT'); + } + showTitle() { + return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + } + + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editPokerEndDate'), + }); + } +} +PokerEndDate.register('pokerEndDate'); diff --git a/client/components/cards/cardDate.styl b/client/components/cards/cardDate.styl index 62cfdcd96..617d11bb6 100644 --- a/client/components/cards/cardDate.styl +++ b/client/components/cards/cardDate.styl @@ -2,11 +2,11 @@ display: block border-radius: 4px padding: 1px 3px - + background-color: #dbdbdb &:hover, &.is-active background-color: #b3b3b3 - + &.current, &.almost-due, &.due, &.long-overdue color: #fff @@ -14,17 +14,17 @@ background-color: #5ba639 &:hover, &.is-active background-color: darken(#5ba639, 10) - + &.almost-due background-color: #edc909 &:hover, &.is-active background-color: darken(#edc909, 10) - + &.due background-color: #fa3f00 &:hover, &.is-active background-color: darken(#fa3f00, 10) - + &.long-overdue background-color: #fd5d47 &:hover, &.is-active @@ -57,3 +57,7 @@ -webkit-font-smoothing: antialiased margin-right: 0.3em +.customfield-date + display: block + border-radius: 4px + padding: 1px 3px diff --git a/client/components/cards/cardDescription.jade b/client/components/cards/cardDescription.jade new file mode 100644 index 000000000..67d20b073 --- /dev/null +++ b/client/components/cards/cardDescription.jade @@ -0,0 +1,7 @@ + +template(name="descriptionForm") + .new-description.js-new-description( + class="{{#if descriptionFormIsOpen}}is-open{{/if}}") + form.js-new-description-form + +editor(class="js-new-description-input" autofocus="autofocus") + | {{getUnsavedValue 'cardDescription' _id getDescription}} diff --git a/client/components/cards/cardDescription.js b/client/components/cards/cardDescription.js new file mode 100644 index 000000000..d83735861 --- /dev/null +++ b/client/components/cards/cardDescription.js @@ -0,0 +1,34 @@ +const descriptionFormIsOpen = new ReactiveVar(false); + +BlazeComponent.extendComponent({ + onDestroyed() { + descriptionFormIsOpen.set(false); + $('.note-popover').hide(); + }, + + descriptionFormIsOpen() { + return descriptionFormIsOpen.get(); + }, + + getInput() { + return this.$('.js-new-description-input'); + }, + + events() { + return [ + { + 'submit .js-card-description'(event) { + event.preventDefault(); + const description = this.currentComponent().getValue(); + this.data().setDescription(description); + }, + // Pressing Ctrl+Enter should submit the form + 'keydown form textarea'(evt) { + if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { + this.find('button[type=submit]').click(); + } + }, + }, + ]; + }, +}).register('descriptionForm'); diff --git a/client/components/cards/cardDescription.styl b/client/components/cards/cardDescription.styl new file mode 100644 index 000000000..fb4cbf23d --- /dev/null +++ b/client/components/cards/cardDescription.styl @@ -0,0 +1,59 @@ +@import 'nib' + +.new-description + position: relative + margin: 0 0 20px 0 + + + &.is-open + .helper + display: inline-block + + textarea + min-height: 100px + color: #4d4d4d + cursor: auto + overflow: hidden + word-wrap: break-word + + .too-long + margin-top: 8px + + textarea + background-color: #fff + border: 0 + box-shadow: 0 1px 2px rgba(0, 0, 0, .23) + height: 36px + margin: 4px 4px 6px 0 + padding: 9px 11px + width: 100% + + &:hover, + &:is-open + background-color: #fff + box-shadow: 0 1px 3px rgba(0, 0, 0, .33) + border: 0 + cursor: pointer + + &:is-open + cursor: auto + +.description-item + background-color: #fff + border: 0 + box-shadow: 0 1px 2px rgba(0, 0, 0, .23) + color: #8c8c8c + height: 36px + margin: 4px 4px 6px 0 + width: 92% + + &:hover + background: darken(white, 12%) + + &.add-description + display: flex + margin: 5px + + a + display: block + margin: auto diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 257ca0a87..e9587a558 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -1,23 +1,26 @@ template(name="cardDetails") - section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas + section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}'): .card-details-canvas .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}') +inlinedForm(classNames="js-card-details-title") +editCardTitleForm else unless isMiniScreen - a.fa.fa-times-thin.close-card-details.js-close-card-details + a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") + unless cardMaximized + a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") + if cardMaximized + a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}") if currentUser.isBoardMember - a.fa.fa-navicon.card-details-menu.js-open-card-details-menu - input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}") + a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") + input.inline-input(type="text" id="cardURL_copy" value="{{ originRelativeUrl }}") a.fa.fa-link.card-copy-button.js-copy-link( class="fa-link" title="{{_ 'copy-card-link-to-clipboard'}}" - value="{{ absoluteUrl }}" ) if isMiniScreen - a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details + a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details(title="{{_ 'close-card'}}") if currentUser.isBoardMember - a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu + 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 h2.card-details-title.js-card-title( class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") @@ -32,7 +35,7 @@ template(name="cardDetails") // else {{_ 'top-level-card'}} if isLinkedCard - h3.linked-card-location + a.linked-card-location.js-go-to-linked-card +viewer | {{getBoardTitle}} > {{getTitle}} @@ -42,242 +45,501 @@ template(name="cardDetails") else p.warning {{_ 'card-archived'}} - .card-details-items - if currentBoard.allowsReceivedDate - .card-details-item.card-details-item-received - h3 - i.fa.fa-sign-out - card-details-item-title {{_ 'card-received'}} - if getReceived - +cardReceivedDate - else + .card-details-left + + .card-details-items + if currentBoard.allowsLabels + .card-details-item.card-details-item-labels + h3.card-details-item-title + i.fa.fa-tags + | {{_ 'labels'}} + a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}") + each labels + span.card-label(class="card-label-{{color}}" title=name) + +viewer + = name if canModifyCard unless currentUser.isWorker - a.card-label.add-label.js-received-date + a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}") i.fa.fa-plus - if currentBoard.allowsStartDate - .card-details-item.card-details-item-start - h3 - i.fa.fa-hourglass-start - card-details-item-title {{_ 'card-start'}} - if getStart - +cardStartDate - else - if canModifyCard - unless currentUser.isWorker - a.card-label.add-label.js-start-date - i.fa.fa-plus + if currentBoard.allowsReceivedDate + hr + .card-details-item.card-details-item-received + h3.card-details-item-title + i.fa.fa-sign-out + | {{_ 'card-received'}} + if getReceived + +cardReceivedDate + else + if canModifyCard + unless currentUser.isWorker + a.card-label.add-label.js-received-date + i.fa.fa-plus - if currentBoard.allowsDueDate - .card-details-item.card-details-item-due - h3 - i.fa.fa-sign-in - card-details-item-title {{_ 'card-due'}} - if getDue - +cardDueDate - else - if canModifyCard - unless currentUser.isWorker - a.card-label.add-label.js-due-date - i.fa.fa-plus + if currentBoard.allowsStartDate + .card-details-item.card-details-item-start + h3.card-details-item-title + i.fa.fa-hourglass-start + | {{_ 'card-start'}} + if getStart + +cardStartDate + else + if canModifyCard + unless currentUser.isWorker + a.card-label.add-label.js-start-date + i.fa.fa-plus - if currentBoard.allowsEndDate - .card-details-item.card-details-item-end - h3 - i.fa.fa-hourglass-end - card-details-item-title {{_ 'card-end'}} - if getEnd - +cardEndDate - else - if canModifyCard - unless currentUser.isWorker - a.card-label.add-label.js-end-date - i.fa.fa-plus + if currentBoard.allowsDueDate + .card-details-item.card-details-item-due + h3.card-details-item-title + i.fa.fa-sign-in + | {{_ 'card-due'}} + if getDue + +cardDueDate + else + if canModifyCard + unless currentUser.isWorker + a.card-label.add-label.js-due-date + i.fa.fa-plus - //.card-details-items - if currentBoard.allowsMembers - .card-details-item.card-details-item-members - h3 - i.fa.fa-users - card-details-item-title {{_ 'members'}} - each getMembers - +userAvatar(userId=this cardId=../_id) + if currentBoard.allowsEndDate + .card-details-item.card-details-item-end + h3.card-details-item-title + i.fa.fa-hourglass-end + | {{_ 'card-end'}} + if getEnd + +cardEndDate + else + if canModifyCard + unless currentUser.isWorker + a.card-label.add-label.js-end-date + i.fa.fa-plus + + hr + if currentBoard.allowsCreator + .card-details-item.card-details-item-creator + h3.card-details-item-title + i.fa.fa-user + | {{_ 'creator'}} + + +userAvatar(userId=userId noRemove=true) | {{! XXX Hack to hide syntaxic coloration /// }} - if canModifyCard - unless currentUser.isWorker - a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}") - i.fa.fa-plus - //if assigneeSelected - if currentBoard.allowsAssignee - .card-details-item.card-details-item-assignees - h3 - i.fa.fa-user - card-details-item-title {{_ 'assignee'}} - each getAssignees - +userAvatarAssignee(userId=this cardId=../_id) - | {{! XXX Hack to hide syntaxic coloration /// }} - if canModifyCard - a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") - i.fa.fa-plus - if currentUser.isWorker - unless assigneeSelected + //.card-details-items + if currentBoard.allowsMembers + .card-details-item.card-details-item-members + h3.card-details-item-title + i.fa.fa-users + | {{_ 'members'}} + each userId in getMembers + +userAvatar(userId=userId cardId=_id) + | {{! XXX Hack to hide syntaxic coloration /// }} + if canModifyCard + unless currentUser.isWorker + a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}") + i.fa.fa-plus + + //if assigneeSelected + if currentBoard.allowsAssignee + .card-details-item.card-details-item-assignees + h3.card-details-item-title + i.fa.fa-user + | {{_ 'assignee'}} + each userId in getAssignees + +userAvatar(userId=userId cardId=_id assignee=true) + | {{! XXX Hack to hide syntaxic coloration /// }} + if canModifyCard a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") i.fa.fa-plus + if currentUser.isWorker + unless assigneeSelected + a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") + i.fa.fa-plus - if currentBoard.allowsLabels - .card-details-item.card-details-item-labels - h3 - i.fa.fa-tags - card-details-item-title {{_ 'labels'}} - a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}") - each labels - span.card-label(class="card-label-{{color}}" title=name) - +viewer - = name - if canModifyCard - unless currentUser.isWorker - a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}") + //.card-details-items + if getSpentTime + .card-details-item.card-details-item-spent + if getIsOvertime + h3.card-details-item-title + | {{_ 'overtime-hours'}} + else + h3.card-details-item-title + | {{_ 'spent-time-hours'}} + +cardSpentTime + + //.card-details-items + if currentBoard.allowsRequestedBy + .card-details-item.card-details-item-name + h3.card-details-item-title + i.fa.fa-shopping-cart + | {{_ 'requested-by'}} + if canModifyCard + unless currentUser.isWorker + +inlinedForm(classNames="js-card-details-requester") + +editCardRequesterForm + else + a.js-open-inlined-form + if getRequestedBy + +viewer + = getRequestedBy + else + | {{_ 'add'}} + else if getRequestedBy + +viewer + = getRequestedBy + + if currentBoard.allowsAssignedBy + .card-details-item.card-details-item-name + h3.card-details-item-title + i.fa.fa-user-plus + | {{_ 'assigned-by'}} + if canModifyCard + unless currentUser.isWorker + +inlinedForm(classNames="js-card-details-assigner") + +editCardAssignerForm + else + a.js-open-inlined-form + if getAssignedBy + +viewer + = getAssignedBy + else + | {{_ 'add'}} + else if getRequestedBy + +viewer + = getAssignedBy + + if currentBoard.allowsCardSortingByNumber + .card-details-item.card-details-sort-order + h3.card-details-item-title + i.fa.fa-sort + | {{_ 'sort'}} + if canModifyCard + +inlinedForm(classNames="js-card-details-sort") + +editCardSortOrderForm + else + a.js-open-inlined-form + +viewer + = sort + + //.card-details-items + if customFieldsWD + hr + each customFieldsWD + .card-details-item.card-details-item-customfield + h3.card-details-item-title + i.fa.fa-list-alt + = definition.name + +cardCustomField + + if getVoteQuestion + hr + .vote-title + div.flex + h3 + i.fa.fa-thumbs-up + | {{_ 'vote-question'}} + if getVoteEnd + +voteEndDate + .vote-result + if votePublic + a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }} + a.card-label.card-label-red.js-show-negative-votes {{ voteCountNegative }} + else + .card-label.card-label-green {{ voteCountPositive }} + .card-label.card-label-red {{ voteCountNegative }} + unless ($and currentBoard.isPublic voteAllowNonBoardMembers ) + .card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} + +viewer + = getVoteQuestion + if showVotingButtons + button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") + if voteState + i.fa.fa-thumbs-up + | {{_ 'vote-for-it'}} + button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") + if $eq voteState false + i.fa.fa-thumbs-down + | {{_ 'vote-against'}} + + if getPokerQuestion + hr + .poker-title + div.flex + h3 + i.fa.fa-thumbs-up + | {{_ 'poker-question'}} + if getPokerEnd + +pokerEndDate + div.flex + .poker-result + if expiredPoker + unless ($and currentBoard.isPublic pokerAllowNonBoardMembers ) + .card-label.card-label-gray {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} + if showPlanningPokerButtons + .poker-result + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-one(class="{{#if $eq pokerState 'one'}}poker-voted{{/if}}") {{_ 'poker-one'}} + if $eq pokerState "one" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-two(class="{{#if $eq pokerState 'two'}}poker-voted{{/if}}") {{_ 'poker-two'}} + if $eq pokerState "two" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-three(class="{{#if $eq pokerState 'three'}}poker-voted{{/if}}") {{_ 'poker-three'}} + if $eq pokerState "three" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-five(class="{{#if $eq pokerState 'five'}}poker-voted{{/if}}") {{_ 'poker-five'}} + if $eq pokerState "five" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-eight(class="{{#if $eq pokerState 'eight'}}poker-voted{{/if}}") {{_ 'poker-eight'}} + if $eq pokerState "eight" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-thirteen(class="{{#if $eq pokerState 'thirteen'}}poker-voted{{/if}}") {{_ 'poker-thirteen'}} + if $eq pokerState "thirteen" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-twenty(class="{{#if $eq pokerState 'twenty'}}poker-voted{{/if}}") {{_ 'poker-twenty'}} + if $eq pokerState "twenty" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-forty(class="{{#if $eq pokerState 'forty'}}poker-voted{{/if}}") {{_ 'poker-forty'}} + if $eq pokerState "forty" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-one-hundred(class="{{#if $eq pokerState 'oneHundred'}}poker-voted{{/if}}") {{_ 'poker-oneHundred'}} + if $eq pokerState "oneHundred" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-unsure(class="{{#if $eq pokerState 'unsure'}}poker-voted{{/if}}") {{_ 'poker-unsure'}} + if $eq pokerState "unsure" + i.fa.fa-check + + if currentUser.isBoardAdmin + button.card-details-blue.js-poker-finish(class="{{#if $eq voteState false}}poker-voted{{/if}}") {{_ 'poker-finish'}} + + if expiredPoker + .poker-table + .poker-table-side-left + .poker-table-heading-left + .poker-table-row + .poker-table-cell + .poker-table-cell + | {{_ 'poker-result-votes' }} + .poker-table-cell.poker-table-cell-who + | {{_ 'poker-result-who' }} + .poker-table-body + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 1}}winner{{else}}loser{{/if}}") {{_ 'poker-one'}} + .poker-table-cell {{ pokerCountOne }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberOne + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 2}}winner{{else}}loser{{/if}}") {{_ 'poker-two'}} + .poker-table-cell {{ pokerCountTwo }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberTwo + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 3}}winner{{else}}loser{{/if}}") {{_ 'poker-three'}} + .poker-table-cell {{ pokerCountThree }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberThree + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 5}}winner{{else}}loser{{/if}}") {{_ 'poker-five'}} + .poker-table-cell {{ pokerCountFive }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberFive + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 8}}winner{{else}}loser{{/if}}") {{_ 'poker-eight'}} + .poker-table-cell {{ pokerCountEight }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberEight + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-side-right + .poker-table-heading-right + .poker-table-row + .poker-table-cell + .poker-table-cell + | {{_ 'poker-result-votes' }} + .poker-table-cell.poker-table-cell-who + | {{_ 'poker-result-who' }} + .poker-table-body + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 13}}winner{{else}}loser{{/if}}") {{_ 'poker-thirteen'}} + .poker-table-cell {{ pokerCountThirteen }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberThirteen + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 20}}winner{{else}}loser{{/if}}") {{_ 'poker-twenty'}} + .poker-table-cell {{ pokerCountTwenty }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberTwenty + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 40}}winner{{else}}loser{{/if}}") {{_ 'poker-forty'}} + .poker-table-cell {{ pokerCountForty }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberForty + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 100}}winner{{else}}loser{{/if}}") {{_ 'poker-oneHundred'}} + .poker-table-cell {{ pokerCountOneHundred }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberOneHundred + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 'unsure'}}winner{{else}}loser{{/if}}") {{_ 'poker-unsure'}} + .poker-table-cell {{ pokerCountUnsure }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberUnsure + a.name + +userAvatar(userId=m._id noRemove=true) + + if currentUser.isBoardAdmin + div.estimation-add + button.card-details-red.js-poker-replay(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'poker-replay'}} + div.estimation-add + button.js-poker-estimation i.fa.fa-plus + | {{_ 'set-estimation'}} + input(type=text,autofocus value=getPokerEstimation,id="pokerEstimation") - //.card-details-items - each customFieldsWD - .card-details-item.card-details-item-customfield - h3.card-details-item-title - +viewer - = definition.name - +cardCustomField - - //.card-details-items - if getSpentTime - .card-details-item.card-details-item-spent - if getIsOvertime - h3.card-details-item-title {{_ 'overtime-hours'}} - else - h3.card-details-item-title {{_ 'spent-time-hours'}} - +cardSpentTime - - //.card-details-items - if currentBoard.allowsRequestedBy - .card-details-item.card-details-item-name - h3 - i.fa.fa-shopping-cart - card-details-item-title {{_ 'requested-by'}} - if canModifyCard - unless currentUser.isWorker - +inlinedForm(classNames="js-card-details-requester") - +editCardRequesterForm - else + //- XXX We should use "editable" to avoid repetiting ourselves + if canModifyCard + unless currentUser.isWorker + if currentBoard.allowsDescriptionTitle + hr + h3.card-details-item-title + i.fa.fa-align-left + | {{_ 'description'}} + if currentBoard.allowsDescriptionText + +inlinedCardDescription(classNames="card-description js-card-description") + +descriptionForm + .edit-controls.clearfix + button.primary(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + if currentBoard.allowsDescriptionText a.js-open-inlined-form - if getRequestedBy + if getDescription +viewer - = getRequestedBy + = getDescription else - | {{_ 'add'}} - else if getRequestedBy - +viewer - = getRequestedBy - - if currentBoard.allowsAssignedBy - .card-details-item.card-details-item-name - h3 - i.fa.fa-user-plus - card-details-item-title {{_ 'assigned-by'}} - if canModifyCard - unless currentUser.isWorker - +inlinedForm(classNames="js-card-details-assigner") - +editCardAssignerForm - else - a.js-open-inlined-form - if getAssignedBy - +viewer - = getAssignedBy - else - | {{_ 'add'}} - else if getRequestedBy - +viewer - = getAssignedBy - - //- XXX We should use "editable" to avoid repetiting ourselves - if canModifyCard - unless currentUser.isWorker + | {{_ 'edit'}} + if (hasUnsavedValue 'cardDescription' _id) + p.quiet + | {{_ 'unsaved-description'}} + a.js-open-inlined-form {{_ 'view-it'}} + = ' - ' + a.js-close-inlined-form {{_ 'discard'}} + else if getDescription if currentBoard.allowsDescriptionTitle hr - h3 - i.fa.fa-align-left - card-details-item-title {{_ 'description'}} + h3.card-details-item-title {{_ 'description'}} if currentBoard.allowsDescriptionText - +inlinedCardDescription(classNames="card-description js-card-description") - +editor(autofocus=true) - | {{getUnsavedValue 'cardDescription' _id getDescription}} - .edit-controls.clearfix - button.primary(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form - else - if currentBoard.allowsDescriptionText - a.js-open-inlined-form - if getDescription - +viewer - = getDescription - else - | {{_ 'edit'}} - if (hasUnsavedValue 'cardDescription' _id) - p.quiet - | {{_ 'unsaved-description'}} - a.js-open-inlined-form {{_ 'view-it'}} - = ' - ' - a.js-close-inlined-form {{_ 'discard'}} - else if getDescription - if currentBoard.allowsDescriptionTitle - hr - h3.card-details-item-title {{_ 'description'}} - if currentBoard.allowsDescriptionText - +viewer - = getDescription + +viewer + = getDescription - .card-checklist-attachmentGalerys - .card-checklist-attachmentGalery.card-checklists - if currentBoard.allowsChecklists + .card-checklist-attachmentGalerys + .card-checklist-attachmentGalery.card-checklists + if currentBoard.allowsChecklists + hr + +checklists(cardId = _id) + if currentBoard.allowsSubtasks + hr + +subtasks(cardId = _id) + if currentBoard.allowsAttachments hr - +checklists(cardId = _id) - if currentBoard.allowsSubtasks - hr - +subtasks(cardId = _id) - if currentBoard.allowsAttachments - hr - h3 - i.fa.fa-paperclip - | {{_ 'attachments'}} - .card-checklist-attachmentGalery.card-attachmentGalery - +attachmentsGalery + h3.card-details-item-title + i.fa.fa-paperclip + | {{_ 'attachments'}} + .card-checklist-attachmentGalery.card-attachmentGalery + +attachmentsGalery - hr - unless currentUser.isNoComments - .activity-title - h3 - i.fa.fa-history - | {{ _ 'activity'}} + .card-details-right + + unless currentUser.isNoComments + .activity-title + h3.card-details-item-title + i.fa.fa-history + | {{ _ 'activity'}} + if currentUser.isBoardMember + .material-toggle-switch(title="{{_ 'hide-system-messages'}}") + //span.toggle-switch-title + if hiddenSystemMessages + input.toggle-switch(type="checkbox" id="toggleButton" checked="checked") + else + input.toggle-switch(type="checkbox" id="toggleButton") + label.toggle-label(for="toggleButton") + if currentBoard.allowsComments if currentUser.isBoardMember - .material-toggle-switch - span.toggle-switch-title {{_ 'hide-system-messages'}} - if hiddenSystemMessages - input.toggle-switch(type="checkbox" id="toggleButton" checked="checked") - else - input.toggle-switch(type="checkbox" id="toggleButton") - label.toggle-label(for="toggleButton") - if currentBoard.allowsComments - if currentUser.isBoardMember - unless currentUser.isNoComments - +commentForm - unless currentUser.isNoComments - if isLoaded.get - if isLinkedCard - +activities(card=this mode="linkedcard") - else if isLinkedBoard - +activities(card=this mode="linkedboard") - else - +activities(card=this mode="card") + unless currentUser.isNoComments + +commentForm + unless currentUser.isNoComments + if isLoaded.get + if isLinkedCard + +activities(card=this mode="linkedcard") + else if isLinkedBoard + +activities(card=this mode="linkedboard") + else + +activities(card=this mode="card") template(name="editCardTitleForm") textarea.js-edit-card-title(rows='1' autofocus dir="auto") @@ -298,6 +560,12 @@ template(name="editCardAssignerForm") button.primary.confirm.js-submit-edit-card-assigner-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form +template(name="editCardSortOrderForm") + input.js-edit-card-sort(type='text' autofocus value=sort dir="auto") + .edit-controls.clearfix + button.primary.confirm.js-submit-edit-card-sort-form(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + template(name="cardDetailsActionsPopup") ul.pop-over-list li @@ -308,17 +576,26 @@ template(name="cardDetailsActionsPopup") else i.fa.fa-eye-slash | {{_ 'watch'}} + hr if canModifyCard unless currentUser.isWorker - hr ul.pop-over-list //li: a.js-members {{_ 'card-edit-members'}} //li: a.js-labels {{_ 'card-edit-labels'}} //li: a.js-attachments {{_ 'card-edit-attachments'}} li - a.js-custom-fields - i.fa.fa-list-alt - | {{_ 'card-edit-custom-fields'}} + a.js-start-voting + i.fa.fa-thumbs-up + | {{_ 'card-edit-voting'}} + li + a.js-start-planning-poker + i.fa.fa-thumbs-up + | {{_ 'card-edit-planning-poker'}} + if currentUser.isBoardAdmin + li + a.js-custom-fields + i.fa.fa-list-alt + | {{_ 'card-edit-custom-fields'}} //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}} //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}} //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}} @@ -331,48 +608,63 @@ template(name="cardDetailsActionsPopup") a.js-set-card-color i.fa.fa-paint-brush | {{_ 'setCardColorPopup-title'}} - hr + hr + ul.pop-over-list + li + a.js-export-card + i.fa.fa-share-alt + | {{_ 'export-card'}} + hr + ul.pop-over-list + li + a.js-move-card-to-top + i.fa.fa-arrow-up + | {{_ 'moveCardToTop-title'}} + li + a.js-move-card-to-bottom + i.fa.fa-arrow-down + | {{_ 'moveCardToBottom-title'}} + hr + ul.pop-over-list + if currentUser.isBoardAdmin + li + a.js-move-card + i.fa.fa-arrow-right + | {{_ 'moveCardPopup-title'}} + unless currentUser.isWorker + li + a.js-copy-card + i.fa.fa-copy + | {{_ 'copyCardPopup-title'}} + unless currentUser.isWorker + hr ul.pop-over-list li - a.js-move-card-to-top - i.fa.fa-arrow-up - | {{_ 'moveCardToTop-title'}} - li - a.js-move-card-to-bottom - i.fa.fa-arrow-down - | {{_ 'moveCardToBottom-title'}} - unless currentUser.isWorker + a.js-copy-checklist-cards + i.fa.fa-list + i.fa.fa-copy + | {{_ 'copyChecklistToManyCardsPopup-title'}} + unless archived hr ul.pop-over-list li - a.js-move-card + a.js-archive i.fa.fa-arrow-right - | {{_ 'moveCardPopup-title'}} - li - a.js-copy-card - i.fa.fa-copy - | {{_ 'copyCardPopup-title'}} - hr - ul.pop-over-list - li - a.js-copy-checklist-cards - i.fa.fa-list - i.fa.fa-copy - | {{_ 'copyChecklistToManyCardsPopup-title'}} - unless archived - hr - ul.pop-over-list - li - a.js-archive - i.fa.fa-arrow-right - i.fa.fa-archive - | {{_ 'archive-card'}} - hr - ul.pop-over-list - li - a.js-more - i.fa.fa-link - | {{_ 'cardMorePopup-title'}} + i.fa.fa-archive + | {{_ 'archive-card'}} + hr + ul.pop-over-list + li + a.js-more + i.fa.fa-link + | {{_ 'cardMorePopup-title'}} + +template(name="exportCardPopup") + ul.pop-over-list + li + a(href="{{exportUrlCardPDF}}",, download="{{exportFilenameCardPDF}}") + i.fa.fa-share-alt + | {{_ 'export-card-pdf'}} template(name="moveCardPopup") +boardsAndLists @@ -390,13 +682,14 @@ template(name="copyChecklistToManyCardsPopup") +boardsAndLists template(name="boardsAndLists") - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each boards - if $eq _id currentBoard._id - option(value="{{_id}}" selected) {{_ 'current'}} - else - option(value="{{_id}}") {{title}} + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each boards + if $eq _id currentBoard._id + option(value="{{_id}}" selected) {{_ 'current'}} + else + option(value="{{_id}}") {{title}} label {{_ 'swimlanes'}}: select.js-select-swimlanes @@ -437,31 +730,14 @@ template(name="cardAssigneesPopup") i.fa.fa-check if currentUser.isWorker ul.pop-over-list.js-card-assignee-list - li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}") - a.name.js-select-assignee(href="#") - +userAvatar(userId=currentUser._id) - span.full-name - = currentUser.profile.fullname - | ({{ currentUser.username }}) - if currentUser.isCardAssignee - i.fa.fa-check - -template(name="userAvatarAssignee") - a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})") - if userData.profile.avatarUrl - img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}") - else - +userAvatarAssigneeInitials(userId=userData._id) - - if showStatus - span.assignee-presence-status(class=presenceStatusClassName) - span.member-type(class=memberType) - - unless isSandstorm - if showEdit - if $eq currentUser._id userData._id - a.edit-avatar.js-change-avatar - i.fa.fa-pencil + li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}") + a.name.js-select-assignee(href="#") + +userAvatar(userId=currentUser._id) + span.full-name + = currentUser.profile.fullname + | ({{ currentUser.username }}) + if currentUser.isCardAssignee + i.fa.fa-check template(name="cardAssigneePopup") .board-assignee-menu @@ -480,18 +756,14 @@ template(name="cardAssigneePopup") with currentUser li: a.js-edit-profile {{_ 'edit-profile'}} -template(name="userAvatarAssigneeInitials") - svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15") - text(x="50%" y="13" text-anchor="middle")= initials - template(name="cardMorePopup") p.quiet span.clearfix span {{_ 'link-card'}} = ' ' i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}") - input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}" autofocus="autofocus") - button.js-copy-card-link-to-clipboard(class="btn") {{_ 'copy-card-link-to-clipboard'}} + input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus") + button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}} span.clearfix br h2 {{_ 'change-card-parent'}} @@ -521,7 +793,8 @@ template(name="cardMorePopup") br | {{_ 'added'}} span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} - a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} + if currentUser.isBoardAdmin + a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} template(name="setCardColorPopup") form.edit-label @@ -536,5 +809,83 @@ template(name="setCardColorPopup") template(name="cardDeletePopup") p {{_ "card-delete-pop"}} unless archived - p {{_ "card-delete-suggest-archive"}} + p {{_ "card-delete-suggest-archive"}} button.js-confirm.negate.full(type="submit") {{_ 'delete'}} + +template(name="deleteVotePopup") + p {{_ "vote-delete-pop"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} + +template(name="cardStartVotingPopup") + form.edit-vote-question + .fields + label(for="vote") {{_ 'vote-question'}} + input.js-vote-field#vote(type="text" name="vote" value="{{getVoteQuestion}}" autofocus disabled="{{#if getVoteQuestion}}disabled{{/if}}") + .check-div + a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-allow-non-members{{/if}}") + .materialCheckBox#vote-allow-non-members(name="vote-allow-non-members" class="{{#if voteAllowNonBoardMembers}}is-checked{{/if}}") + span {{_ 'allowNonBoardMembers'}} + .check-div + a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-public{{/if}}") + .materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}") + span {{_ 'vote-public'}} + .check-div.flex + i.fa.fa-hourglass-end + a.js-end-date + span + | {{_ 'card-end'}} + unless getVoteEnd + i.fa.fa-plus + if getVoteEnd + +voteEndDate + + button.primary.js-submit {{_ 'save'}} + if getVoteQuestion + if currentUser.isBoardAdmin + button.js-remove-vote.negate.wide.right {{_ 'delete'}} + +template(name="positiveVoteMembersPopup") + ul.pop-over-list.js-card-member-list + each m in voteMemberPositive + li.item + a.name + +userAvatar(userId=m._id) + span.full-name + = m.profile.fullname + | ({{ m.username }}) + +template(name="negativeVoteMembersPopup") + ul.pop-over-list.js-card-member-list + each m in voteMemberNegative + li.item + a.name + +userAvatar(userId=m._id) + span.full-name + = m.profile.fullname + | ({{ m.username }}) + +template(name="deletePokerPopup") + p {{_ "poker-delete-pop"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} + +template(name="cardStartPlanningPokerPopup") + form.edit-poker-question + .fields + .check-div + a.flex(class="{{#if getPokerQuestion}}is-disabled{{else}}js-toggle-poker-allow-non-members{{/if}}") + .materialCheckBox#poker-allow-non-members(name="poker-allow-non-members" class="{{#if pokerAllowNonBoardMembers}}is-checked{{/if}}") + span {{_ 'allowNonBoardMembers'}} + .check-div.flex + i.fa.fa-hourglass-end + a.js-end-date + span + | {{_ 'card-end'}} + unless getPokerEnd + i.fa.fa-plus + if getPokerEnd + +pokerEndDate + + button.primary.js-submit {{_ 'save'}} + if getPokerQuestion + if currentUser.isBoardAdmin + button.js-remove-poker.negate.wide.right {{_ 'delete'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 5fdc55797..ecc72f7c4 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,14 +1,21 @@ -const subManager = new SubsManager(); -const { calculateIndexData, enableClickOnTouch } = Utils; +import { DatePicker } from '/client/lib/datepicker'; +import Cards from '/models/cards'; +import Boards from '/models/boards'; +import Checklists from '/models/checklists'; +import Integrations from '/models/integrations'; +import Users from '/models/users'; +import Lists from '/models/lists'; +import CardComments from '/models/cardComments'; +import { ALLOWED_COLORS } from '/config/const'; +import moment from 'moment'; +import { UserAvatar } from '../users/userAvatar'; -let cardColors; -Meteor.startup(() => { - cardColors = Cards.simpleSchema()._schema.color.allowedValues; -}); +const subManager = new SubsManager(); +const { calculateIndexData } = Utils; BlazeComponent.extendComponent({ mixins() { - return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; + return [Mixins.InfiniteScrolling]; }, calculateNextPeak() { @@ -47,6 +54,10 @@ BlazeComponent.extendComponent({ return Meteor.user().hasHiddenSystemMessages(); }, + cardMaximized() { + return Meteor.user().hasCardMaximized(); + }, + canModifyCard() { return ( Meteor.user() && @@ -57,12 +68,19 @@ BlazeComponent.extendComponent({ }, scrollParentContainer() { - const cardPanelWidth = 510; - const bodyBoardComponent = this.parentComponent().parentComponent(); + const cardPanelWidth = 600; + const parentComponent = this.parentComponent(); + // TODO sometimes parentComponent is not available, maybe because it's not + // yet created?! + 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'); + // TODO sometimes cardContainer is not available, maybe because it's not yet + // created?! + if (!$cardContainer) return; const cardContainerScroll = $cardContainer.scrollLeft(); const cardContainerWidth = $cardContainer.width(); @@ -107,7 +125,7 @@ BlazeComponent.extendComponent({ if (card) { const board = Boards.findOne(card.boardId); if (board) { - result = FlowRouter.url('card', { + result = FlowRouter.path('card', { boardId: card.boardId, slug: board.slug, cardId: card._id, @@ -117,6 +135,24 @@ BlazeComponent.extendComponent({ return result; }, + showVotingButtons() { + const card = this.currentData(); + return ( + (currentUser.isBoardMember() || + (currentUser && card.voteAllowNonBoardMembers())) && + !card.expiredVote() + ); + }, + + showPlanningPokerButtons() { + const card = this.currentData(); + return ( + (currentUser.isBoardMember() || + (currentUser && card.pokerAllowNonBoardMembers())) && + !card.expiredPoker() + ); + }, + onRendered() { if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) { // Send Webhook but not create Activities records --- @@ -138,15 +174,13 @@ BlazeComponent.extendComponent({ }).fetch(); if (integrations.length > 0) { - integrations.forEach(integration => { + integrations.forEach((integration) => { Meteor.call( 'outgoingWebhooks', integration, 'CardSelected', params, - () => { - return; - }, + () => {}, ); }); } @@ -155,13 +189,6 @@ BlazeComponent.extendComponent({ if (!Utils.isMiniScreen()) { Meteor.setTimeout(() => { - $('.card-details').mCustomScrollbar({ - theme: 'minimal-dark', - setWidth: false, - setLeft: 0, - scrollbarPosition: 'outside', - mouseWheel: true, - }); this.scrollParentContainer(); }, 500); } @@ -200,9 +227,6 @@ BlazeComponent.extendComponent({ }, }); - // ugly touch event hotfix - enableClickOnTouch('.card-checklist-items .js-checklist'); - const $subtasksDom = this.$('.card-subtasks-items'); $subtasksDom.sortable({ @@ -238,26 +262,24 @@ BlazeComponent.extendComponent({ }, }); - // ugly touch event hotfix - enableClickOnTouch('.card-subtasks-items .js-subtasks'); - function userIsMember() { return Meteor.user() && Meteor.user().isBoardMember(); } // Disable sorting if the current user is not a board member this.autorun(() => { - if ($checklistsDom.data('sortable')) { - $checklistsDom.sortable('option', 'disabled', !userIsMember()); + const disabled = !userIsMember(); + if ( + $checklistsDom.data('uiSortable') || + $checklistsDom.data('sortable') + ) { + $checklistsDom.sortable('option', 'disabled', disabled); + if (Utils.isMiniScreenOrShowDesktopDragHandles()) { + $checklistsDom.sortable({ handle: '.checklist-handle' }); + } } - if ($subtasksDom.data('sortable')) { - $subtasksDom.sortable('option', 'disabled', !userIsMember()); - } - if ($checklistsDom.data('sortable')) { - $checklistsDom.sortable('option', 'disabled', Utils.isMiniScreen()); - } - if ($subtasksDom.data('sortable')) { - $subtasksDom.sortable('option', 'disabled', Utils.isMiniScreen()); + if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) { + $subtasksDom.sortable('option', 'disabled', disabled); } }); }, @@ -286,7 +308,9 @@ BlazeComponent.extendComponent({ Utils.goBoardId(this.data().boardId); }, 'click .js-copy-link'() { - StringToCopyElement = document.getElementById('cardURL_copy'); + const StringToCopyElement = document.getElementById('cardURL_copy'); + StringToCopyElement.value = + window.location.origin + window.location.pathname; StringToCopyElement.select(); if (document.execCommand('copy')) { StringToCopyElement.blur(); @@ -316,9 +340,7 @@ BlazeComponent.extendComponent({ }, 'submit .js-card-details-title'(event) { event.preventDefault(); - const title = this.currentComponent() - .getValue() - .trim(); + const title = this.currentComponent().getValue().trim(); if (title) { this.data().setTitle(title); } else { @@ -327,9 +349,7 @@ BlazeComponent.extendComponent({ }, 'submit .js-card-details-assigner'(event) { event.preventDefault(); - const assigner = this.currentComponent() - .getValue() - .trim(); + const assigner = this.currentComponent().getValue().trim(); if (assigner) { this.data().setAssignedBy(assigner); } else { @@ -338,15 +358,26 @@ BlazeComponent.extendComponent({ }, 'submit .js-card-details-requester'(event) { event.preventDefault(); - const requester = this.currentComponent() - .getValue() - .trim(); + const requester = this.currentComponent().getValue().trim(); if (requester) { this.data().setRequestedBy(requester); } else { this.data().setRequestedBy(''); } }, + 'submit .js-card-details-sort'(event) { + event.preventDefault(); + const sort = parseFloat(this.currentComponent() + .getValue() + .trim()); + if (!Number.isNaN(sort)) { + let card = this.data(); + card.move(card.boardId, card.swimlaneId, card.listId, sort); + } + }, + 'click .js-go-to-linked-card'() { + Utils.goCardId(this.data().linkedId); + }, 'click .js-member': Popup.open('cardMember'), 'click .js-add-members': Popup.open('cardMembers'), 'click .js-assignee': Popup.open('cardAssignee'), @@ -356,6 +387,8 @@ BlazeComponent.extendComponent({ 'click .js-start-date': Popup.open('editCardStartDate'), 'click .js-due-date': Popup.open('editCardDueDate'), 'click .js-end-date': Popup.open('editCardEndDate'), + 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'), + 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'), 'mouseenter .js-card-details'() { const parentComponent = this.parentComponent().parentComponent(); //on mobile view parent is Board, not BoardBody. @@ -379,125 +412,144 @@ BlazeComponent.extendComponent({ 'click #toggleButton'() { Meteor.call('toggleSystemMessages'); }, + 'click .js-maximize-card-details'() { + Meteor.call('toggleCardMaximized'); + autosize($('.card-details')); + }, + 'click .js-minimize-card-details'() { + Meteor.call('toggleCardMaximized'); + autosize($('.card-details')); + }, + 'click .js-vote'(e) { + const forIt = $(e.target).hasClass('js-vote-positive'); + let newState = null; + if ( + this.data().voteState() === null || + (this.data().voteState() === false && forIt) || + (this.data().voteState() === true && !forIt) + ) { + newState = forIt; + } + this.data().setVote(Meteor.userId(), newState); + }, + 'click .js-poker'(e) { + let newState = null; + if ($(e.target).hasClass('js-poker-vote-one')) { + newState = 'one'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-two')) { + newState = 'two'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-three')) { + newState = 'three'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-five')) { + newState = 'five'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-eight')) { + newState = 'eight'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-thirteen')) { + newState = 'thirteen'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-twenty')) { + newState = 'twenty'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-forty')) { + newState = 'forty'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-one-hundred')) { + newState = 'oneHundred'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-unsure')) { + newState = 'unsure'; + this.data().setPoker(Meteor.userId(), newState); + } + }, + 'click .js-poker-finish'(e) { + if ($(e.target).hasClass('js-poker-finish')) { + e.preventDefault(); + const now = moment().format('YYYY-MM-DD HH:mm'); + this.data().setPokerEnd(now); + } + }, + + 'click .js-poker-replay'(e) { + if ($(e.target).hasClass('js-poker-replay')) { + e.preventDefault(); + this.currentCard = this.currentData(); + this.currentCard.replayPoker(); + this.data().unsetPokerEnd(); + this.data().unsetPokerEstimation(); + } + }, + 'click .js-poker-estimation'(event) { + event.preventDefault(); + + const ruleTitle = this.find('#pokerEstimation').value; + if (ruleTitle !== undefined && ruleTitle !== '') { + this.find('#pokerEstimation').value = ''; + + if (ruleTitle) { + this.data().setPokerEstimation(parseInt(ruleTitle, 10)); + } else { + this.data().setPokerEstimation(''); + } + } + }, }, ]; }, }).register('cardDetails'); -Template.cardDetails.helpers({ - userData() { - // We need to handle a special case for the search results provided by the - // `matteodem:easy-search` package. Since these results gets published in a - // separate collection, and not in the standard Meteor.Users collection as - // expected, we use a component parameter ("property") to distinguish the - // two cases. - const userCollection = this.esSearch ? ESSearchResults : Users; - return userCollection.findOne(this.userId, { - fields: { - profile: 1, - username: 1, - }, - }); +BlazeComponent.extendComponent({ + template() { + return 'exportCard'; }, + withApi() { + return Template.instance().apiEnabled.get(); + }, + exportUrlCardPDF() { + const params = { + boardId: Session.get('currentBoard'), + listId: this.listId, + cardId: this.cardId, + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path( + '/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', + params, + queryParams, + ); + }, + exportFilenameCardPDF() { + //const boardId = Session.get('currentBoard'); + //return `export-card-pdf-${boardId}.xlsx`; + return `export-card.pdf`; + }, +}).register('exportCardPopup'); - receivedSelected() { - if (this.getReceived().length === 0) { - return false; - } else { - return true; - } - }, - - startSelected() { - if (this.getStart().length === 0) { - return false; - } else { - return true; - } - }, - - endSelected() { - if (this.getEnd().length === 0) { - return false; - } else { - return true; - } - }, - - dueSelected() { - if (this.getDue().length === 0) { - return false; - } else { - return true; - } - }, - - memberSelected() { - if (this.getMembers().length === 0) { - return false; - } else { - return true; - } - }, - - labelSelected() { - if (this.getLabels().length === 0) { - return false; - } else { - return true; - } - }, - - assigneeSelected() { - if (this.getAssignees().length === 0) { - return false; - } else { - return true; - } - }, - - requestBySelected() { - if (this.getRequestBy().length === 0) { - return false; - } else { - return true; - } - }, - - assigneeBySelected() { - if (this.getAssigneeBy().length === 0) { - return false; - } else { - return true; - } - }, - - memberType() { - const user = Users.findOne(this.userId); - return user && user.isBoardAdmin() ? 'admin' : 'normal'; - }, - - presenceStatusClassName() { - const user = Users.findOne(this.userId); - const userPresence = presences.findOne({ userId: this.userId }); - if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending'; - else if (!userPresence) return 'disconnected'; - else if (Session.equals('currentBoard', userPresence.state.currentBoardId)) - return 'active'; - else return 'idle'; - }, -}); - -Template.userAvatarAssigneeInitials.helpers({ - initials() { - const user = Users.findOne(this.userId); - return user && user.getInitials(); - }, - - viewPortWidth() { - const user = Users.findOne(this.userId); - return ((user && user.getInitials().length) || 1) * 12; - }, +// only allow number input +Template.editCardSortOrderForm.onRendered(function() { + this.$('input').on("keypress paste", function(event) { + let keyCode = event.keyCode; + let charCode = String.fromCharCode(keyCode); + let regex = new RegExp('[-0-9.]'); + let ret = regex.test(charCode); + // only working here, defining in events() doesn't handle the return value correctly + return ret; + }); }); // We extends the normal InlinedForm component to support UnsavedEdits draft @@ -546,6 +598,10 @@ Template.cardDetailsActionsPopup.helpers({ return this.findWatcher(Meteor.userId()); }, + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + canModifyCard() { return ( Meteor.user() && @@ -556,10 +612,13 @@ Template.cardDetailsActionsPopup.helpers({ }); Template.cardDetailsActionsPopup.events({ + 'click .js-export-card': Popup.open('exportCard'), 'click .js-members': Popup.open('cardMembers'), 'click .js-assignees': Popup.open('cardAssignees'), 'click .js-labels': Popup.open('cardLabels'), 'click .js-attachments': Popup.open('cardAttachments'), + 'click .js-start-voting': Popup.open('cardStartVoting'), + 'click .js-start-planning-poker': Popup.open('cardStartPlanningPoker'), 'click .js-custom-fields': Popup.open('cardCustomFields'), 'click .js-received-date': Popup.open('editCardReceivedDate'), 'click .js-start-date': Popup.open('editCardStartDate'), @@ -575,7 +634,7 @@ Template.cardDetailsActionsPopup.events({ const minOrder = _.min( this.list() .cards(this.swimlaneId) - .map(c => c.sort), + .map((c) => c.sort), ); this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); }, @@ -584,7 +643,7 @@ Template.cardDetailsActionsPopup.events({ const maxOrder = _.max( this.list() .cards(this.swimlaneId) - .map(c => c.sort), + .map((c) => c.sort), ); this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); }, @@ -603,7 +662,7 @@ Template.cardDetailsActionsPopup.events({ }, }); -Template.editCardTitleForm.onRendered(function() { +Template.editCardTitleForm.onRendered(function () { autosize(this.$('.js-edit-card-title')); }); @@ -617,7 +676,7 @@ Template.editCardTitleForm.events({ }, }); -Template.editCardRequesterForm.onRendered(function() { +Template.editCardRequesterForm.onRendered(function () { autosize(this.$('.js-edit-card-requester')); }); @@ -630,7 +689,7 @@ Template.editCardRequesterForm.events({ }, }); -Template.editCardAssignerForm.onRendered(function() { +Template.editCardAssignerForm.onRendered(function () { autosize(this.$('.js-edit-card-assigner')); }); @@ -649,7 +708,11 @@ Template.moveCardPopup.events({ // instead from a “component” state. const card = Cards.findOne(Session.get('currentCard')); const bSelect = $('.js-select-boards')[0]; - const boardId = bSelect.options[bSelect.selectedIndex].value; + let boardId; + // if we are a worker, we won't have a board select so we just use the + // current boardId of the card. + if (bSelect) boardId = bSelect.options[bSelect.selectedIndex].value; + else boardId = card.boardId; const lSelect = $('.js-select-lists')[0]; const listId = lSelect.options[lSelect.selectedIndex].value; const slSelect = $('.js-select-swimlanes')[0]; @@ -665,17 +728,16 @@ BlazeComponent.extendComponent({ }, boards() { - const boards = Boards.find( + return Boards.find( { archived: false, 'members.userId': Meteor.userId(), _id: { $ne: Meteor.user().getTemplatesBoardId() }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); - return boards; }, swimlanes() { @@ -704,7 +766,7 @@ Template.copyCardPopup.events({ 'click .js-done'() { const card = Cards.findOne(Session.get('currentCard')); const lSelect = $('.js-select-lists')[0]; - listId = lSelect.options[lSelect.selectedIndex].value; + const listId = lSelect.options[lSelect.selectedIndex].value; const slSelect = $('.js-select-swimlanes')[0]; const swimlaneId = slSelect.options[slSelect.selectedIndex].value; const bSelect = $('.js-select-boards')[0]; @@ -712,9 +774,7 @@ Template.copyCardPopup.events({ const textarea = $('#copy-card-title'); const title = textarea.val().trim(); // insert new card to the bottom of new list - card.sort = Lists.findOne(card.listId) - .cards() - .count(); + card.sort = Lists.findOne(card.listId).cards().count(); if (title) { card.title = title; @@ -745,9 +805,7 @@ Template.copyChecklistToManyCardsPopup.events({ const textarea = $('#copy-card-title'); const titleEntry = textarea.val().trim(); // insert new card to the bottom of new list - card.sort = Lists.findOne(card.listId) - .cards() - .count(); + card.sort = Lists.findOne(card.listId).cards().count(); if (titleEntry) { const titleList = JSON.parse(titleEntry); @@ -764,13 +822,13 @@ Template.copyChecklistToManyCardsPopup.events({ Filter.addException(_id); // copy checklists - Checklists.find({ cardId: oldId }).forEach(ch => { + Checklists.find({ cardId: oldId }).forEach((ch) => { ch.copy(_id); }); // copy subtasks - cursor = Cards.find({ parentId: oldId }); - cursor.forEach(function() { + const cursor = Cards.find({ parentId: oldId }); + cursor.forEach(function () { 'use strict'; const subtask = arguments[0]; subtask.parentId = _id; @@ -779,7 +837,7 @@ Template.copyChecklistToManyCardsPopup.events({ }); // copy card comments - CardComments.find({ cardId: oldId }).forEach(cmt => { + CardComments.find({ cardId: oldId }).forEach((cmt) => { cmt.copy(_id); }); } @@ -795,7 +853,7 @@ BlazeComponent.extendComponent({ }, colors() { - return cardColors.map(color => ({ color, name: '' })); + return ALLOWED_COLORS.map((color) => ({ color, name: '' })); }, isSelected(color) { @@ -839,7 +897,7 @@ BlazeComponent.extendComponent({ }, boards() { - const boards = Boards.find( + return Boards.find( { archived: false, 'members.userId': Meteor.userId(), @@ -848,10 +906,9 @@ BlazeComponent.extendComponent({ }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); - return boards; }, cards() { @@ -919,9 +976,25 @@ BlazeComponent.extendComponent({ } } }, - 'click .js-delete': Popup.afterConfirm('cardDelete', function() { + 'click .js-delete': Popup.afterConfirm('cardDelete', function () { Popup.close(); - Cards.remove(this._id); + // verify that there are no linked cards + if (Cards.find({ linkedId: this._id }).count() === 0) { + Cards.remove(this._id); + } else { + // TODO: Maybe later we can list where the linked cards are. + // Now here is popup with a hint that the card cannot be deleted + // as there are linked cards. + // Related: + // client/components/lists/listHeader.js about line 248 + // https://github.com/wekan/wekan/issues/2785 + const message = `${TAPi18n.__( + 'delete-linked-card-before-this-card', + )} linkedId: ${ + this._id + } at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`; + alert(message); + } Utils.goBoardId(this.boardId); }), 'change .js-field-parent-board'(event) { @@ -945,6 +1018,528 @@ BlazeComponent.extendComponent({ }, }).register('cardMorePopup'); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.voteQuestion = new ReactiveVar(this.currentCard.voteQuestion); + }, + + events() { + return [ + { + 'click .js-end-date': Popup.open('editVoteEndDate'), + 'submit .edit-vote-question'(evt) { + evt.preventDefault(); + const voteQuestion = evt.target.vote.value; + const publicVote = $('#vote-public').hasClass('is-checked'); + const allowNonBoardMembers = $('#vote-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getVoteEnd(); + + this.currentCard.setVoteQuestion( + voteQuestion, + publicVote, + allowNonBoardMembers, + ); + if (endString) { + this.currentCard.setVoteEnd(endString); + } + Popup.close(); + }, + 'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => { + event.preventDefault(); + this.currentCard.unsetVote(); + Popup.close(); + }), + 'click a.js-toggle-vote-public'(event) { + event.preventDefault(); + $('#vote-public').toggleClass('is-checked'); + }, + 'click a.js-toggle-vote-allow-non-members'(event) { + event.preventDefault(); + $('#vote-allow-non-members').toggleClass('is-checked'); + }, + }, + ]; + }, +}).register('cardStartVotingPopup'); + +// editVoteEndDatePopup +(class extends DatePicker { + onCreated() { + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd())); + } + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + moment(new Date().setHours(12, 0, 0)).format('LT'); + + const dateString = `${evt.target.date.value} ${time}`; + + /* + const newDate = moment(dateString, 'L LT', true); + if (newDate.isValid()) { + // if active vote - store it + if (this.currentData().getVoteQuestion()) { + this._storeDate(newDate.toDate()); + Popup.close(); + } else { + this.currentData().vote = { end: newDate.toDate() }; // set vote end temp + Popup.back(); + } + + + */ + + // Try to parse different date formats of all languages. + // This code is same for vote and planning poker. + const usaDate = moment(dateString, 'L LT', true); + const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true); + const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true); + const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true); + const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true); + const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true); + const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true); + const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true); + const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true); + const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true); + const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true); + // greekDate does not work: el Greek Ελληνικά , + // it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM + // where MM is maybe some text like AM/PM ? + // Also some other languages that have non-ascii characters in dates + // do not work. + const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true); + const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true); + + if (usaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(usaDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (euroAmDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euroAmDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (euro24hDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euro24hDate.toDate()); + this.card.setPokerEnd(euro24hDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (eurodotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(eurodotDate.toDate()); + this.card.setPokerEnd(eurodotDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (minusDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(minusDate.toDate()); + this.card.setPokerEnd(minusDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (slashDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(slashDate.toDate()); + this.card.setPokerEnd(slashDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (dotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(dotDate.toDate()); + this.card.setPokerEnd(dotDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (brezhonegDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(brezhonegDate.toDate()); + this.card.setPokerEnd(brezhonegDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (hrvatskiDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(hrvatskiDate.toDate()); + this.card.setPokerEnd(hrvatskiDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (latviaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(latviaDate.toDate()); + this.card.setPokerEnd(latviaDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (nederlandsDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(nederlandsDate.toDate()); + this.card.setPokerEnd(nederlandsDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (greekDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(greekDate.toDate()); + this.card.setPokerEnd(greekDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (macedonianDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(macedonianDate.toDate()); + this.card.setPokerEnd(macedonianDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp + Popup.back(); + } + } else { + this.error.set('invalid-date'); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.close(); + }, + }, + ]; + } + _storeDate(newDate) { + this.card.setVoteEnd(newDate); + } + _deleteDate() { + this.card.unsetVoteEnd(); + } +}.register('editVoteEndDatePopup')); + +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.pokerQuestion = new ReactiveVar(this.currentCard.pokerQuestion); + }, + + events() { + return [ + { + 'click .js-end-date': Popup.open('editPokerEndDate'), + 'submit .edit-poker-question'(evt) { + evt.preventDefault(); + const pokerQuestion = true; + const allowNonBoardMembers = $('#poker-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getPokerEnd(); + + this.currentCard.setPokerQuestion( + pokerQuestion, + allowNonBoardMembers, + ); + if (endString) { + this.currentCard.setPokerEnd(endString); + } + Popup.close(); + }, + 'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => { + event.preventDefault(); + this.currentCard.unsetPoker(); + Popup.close(); + }), + 'click a.js-toggle-poker-allow-non-members'(event) { + event.preventDefault(); + $('#poker-allow-non-members').toggleClass('is-checked'); + }, + }, + ]; + }, +}).register('cardStartPlanningPokerPopup'); + +// editPokerEndDatePopup +(class extends DatePicker { + onCreated() { + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + this.data().getPokerEnd() && + this.date.set(moment(this.data().getPokerEnd())); + } + + /* + Tried to use dateFormat and timeFormat from client/components/lib/datepicker.js + to make detecting all date formats not necessary, + but got error "language mk does not exist". + Maybe client/components/lib/datepicker.jade could have hidden input field for + datepicker format that could be used to detect date format? + + dateFormat() { + return moment.localeData().longDateFormat('L'); + } + + timeFormat() { + return moment.localeData().longDateFormat('LT'); + } + + const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true); + */ + + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + moment(new Date().setHours(12, 0, 0)).format('LT'); + + const dateString = `${evt.target.date.value} ${time}`; + + /* + Tried to use dateFormat and timeFormat from client/components/lib/datepicker.js + to make detecting all date formats not necessary, + but got error "language mk does not exist". + Maybe client/components/lib/datepicker.jade could have hidden input field for + datepicker format that could be used to detect date format? + + const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true); + + if (newDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(newDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: newDate.toDate() }; // set poker end temp + Popup.back(); + } + */ + + // Try to parse different date formats of all languages. + // This code is same for vote and planning poker. + const usaDate = moment(dateString, 'L LT', true); + const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true); + const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true); + const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true); + const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true); + const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true); + const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true); + const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true); + const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true); + const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true); + const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true); + // greekDate does not work: el Greek Ελληνικά , + // it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM + // where MM is maybe some text like AM/PM ? + // Also some other languages that have non-ascii characters in dates + // do not work. + const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true); + const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true); + + if (usaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(usaDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (euroAmDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euroAmDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (euro24hDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euro24hDate.toDate()); + this.card.setPokerEnd(euro24hDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (eurodotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(eurodotDate.toDate()); + this.card.setPokerEnd(eurodotDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (minusDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(minusDate.toDate()); + this.card.setPokerEnd(minusDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (slashDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(slashDate.toDate()); + this.card.setPokerEnd(slashDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (dotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(dotDate.toDate()); + this.card.setPokerEnd(dotDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (brezhonegDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(brezhonegDate.toDate()); + this.card.setPokerEnd(brezhonegDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (hrvatskiDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(hrvatskiDate.toDate()); + this.card.setPokerEnd(hrvatskiDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (latviaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(latviaDate.toDate()); + this.card.setPokerEnd(latviaDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (nederlandsDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(nederlandsDate.toDate()); + this.card.setPokerEnd(nederlandsDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (greekDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(greekDate.toDate()); + this.card.setPokerEnd(greekDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (macedonianDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(macedonianDate.toDate()); + this.card.setPokerEnd(macedonianDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp + Popup.back(); + } + } else { + // this.error.set('invalid-date); + this.error.set('invalid-date' + ' ' + dateString); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.close(); + }, + }, + ]; + } + _storeDate(newDate) { + this.card.setPokerEnd(newDate); + } + _deleteDate() { + this.card.unsetPokerEnd(); + } +}.register('editPokerEndDatePopup')); + // Close the card details pane by pressing escape EscapeActions.register( 'detailsPane', diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 80fa87c07..486b211a0 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -10,6 +10,9 @@ avatar-radius = 50% left: -2000px top: 0px +#clipboard + white-space: normal + .assignee border-radius: 3px display: block @@ -37,6 +40,8 @@ avatar-radius = 50% position: absolute &.avatar-image + object-fit: cover; + object-position: center; height: 100% width: @height @@ -84,7 +89,7 @@ avatar-radius = 50% .card-details padding: 0 flex-shrink: 0 - flex-basis: 510px + flex-basis: 600px will-change: flex-basis overflow-y: scroll overflow-x: hidden @@ -94,25 +99,24 @@ avatar-radius = 50% animation: flexGrowIn 0.1s box-shadow: 0 0 7px 0 darken(white, 30%) transition: flex-basis 0.1s + box-sizing: border-box .mCustomScrollBox padding-left: 0 - .ps-scrollbar-y-rail - pointer-event: all - position: absolute; - .card-details-canvas - width: 470px - padding-left: 20px; + width: auto + padding: 0 20px .card-details-header margin: 0 -20px 5px - padding 7px 16px + padding: 7px 20px background: darken(white, 7%) border-bottom: 1px solid darken(white, 14%) .close-card-details, + .maximize-card-details, + .minimize-card-details, .card-details-menu, .card-copy-button, .card-copy-mobile-button, @@ -120,9 +124,11 @@ avatar-radius = 50% .card-details-menu-mobile-web float: right - .close-card-details + .close-card-details, + .maximize-card-details, + .minimize-card-details font-size: 24px - padding: 5px + padding: 5px 10px 5px 10px margin-right: -8px .close-card-details-mobile-web @@ -196,23 +202,33 @@ avatar-radius = 50% margin-right: 0.5em &:last-child margin-right: 0 - &.card-details-item-labels, + &.card-details-item-labels + display: block + word-wrap: break-word + max-width: 95% + flex-grow: 1 &.card-details-item-members, &.card-details-item-assignees, - &.card-details-item-received, - &.card-details-item-start, - &.card-details-item-due, - &.card-details-item-end, &.card-details-item-customfield, &.card-details-item-name display: block word-wrap: break-word - max-width: 48% + max-width: 36% + flex-grow: 1 + &.card-details-item-creator, + &.card-details-item-received, + &.card-details-item-start, + &.card-details-item-due, + &.card-details-item-end + display: block + word-wrap: break-word + max-width: 28% flex-grow: 1 .card-details-item-title font-size: 16px - color: #000 + font-weight: bold + color: #4d4d4d .card-label padding-top: 5px @@ -221,6 +237,43 @@ avatar-radius = 50% .activities padding-top: 10px +.card-details-maximized + padding: 0 + flex-shrink: 0 + flex-basis: calc(100% - 20px) + will-change: flex-basis + overflow-y: scroll + overflow-x: scroll + background: darken(white, 3%) + border-radius: bottom 3px + z-index: 1000 !important + animation: flexGrowIn 0.1s + box-shadow: 0 0 7px 0 darken(white, 30%) + transition: flex-basis 0.1s + box-sizing: border-box + position: absolute + top: 0 + left: 0 + height: calc(100% - 20px) + width: calc(100% - 20px) + float: left + + .card-details-left + position: absolute + float: left + top: 60px + left: 20px + width: 47% + + .card-details-right + position: absolute + float: right + top: 20px + left: 50% + + .card-details-header + width: 47% + input[type="text"].attachment-add-link-input float: left margin: 0 0 8px @@ -241,14 +294,20 @@ input[type="submit"].attachment-add-link-submit .card-details-canvas width: 100% - padding-left: 0px; + padding-left: 0px .card-details-header .close-card-details margin-right: 0px .card-details-menu - margin-right: 10px + margin-right: 40px + + .maximize-card-details + margin-right: 40px + + .minimize-card-details + margin-right: 40px card-details-color(background, color...) background: background !important @@ -330,3 +389,146 @@ card-details-color(background, color...) .card-details-indigo card-details-color(#4b0082, #ffffff) //White text for better visibility + +.voted + opacity: .7 +.vote-title + display: flex + justify-content: space-between + + .js-edit-date + align-self: baseline + margin-left: 5px + +.vote-result + display: flex +.js-show-positive-votes + cursor: pointer + +.poker-voted + opacity: .7 + +.poker-title + display: flex + justify-content: space-between + + .js-edit-date + align-self: baseline + margin-left: 5px + +.poker-result + display: flex + flex-flow: row wrap +.js-show-positive-poker-votes + cursor: pointer + +.poker-deck + display: grid + flex-direction: column + text-align: center + +.poker-card-result + width: 32px + font-size: 1em + font-weight: bold + padding: 4px 2px 4px 2px + cursor: default + +.winner + font-weight: bold + outline: #2d2d2d solid 2px + +.loser + opacity: .5 + +.responsive-table + overflow-x: auto + +.poker-table + display: table + width: 100% + padding-top: 10px + +.poker-table-row + display: table-row + +.poker-table-heading + background-color: #EEE + display: table-header-group + +.poker-table-cell + display: table-cell + padding: 0 0 5px 2px + border-bottom: 1px solid #d2d0d0 + text-align: center + min-width: 45px + +.poker-table-cell-who + width: 150px + vertical-align: middle + +.poker-table-heading-left, +.poker-table-heading-right + display: table-header-group + font-weight: bold + border-top: 1px solid #808080 + +@media (max-width: 400px) + .poker-table-heading-right + display: none + +.poker-table-body + display: table-row-group + +.poker-table-side-left, +.poker-table-side-right + display: inline-block + +.poker-table-side-right + padding-left: 10px + +@media (max-width: 400px) + .poker-table-side-right + padding-left: 0px + +.estimation-add + display: block + overflow: auto + margin-top: 15px + margin-bottom: 5px + input + display: inline-block + float: right + margin: auto + margin-right: 10px + width: 100px + button + display: inline-block + float: right + margin: auto + +.poker-card + width:48px + height:72px + float:left + background:#fff + border-radius:5px + display:table + box-sizing:border-box + padding:5px + margin:3px + font-size:20px + font-weight: bold + text-shadow: #2d2d2d 1px 1px 0 + box-shadow:0 0 5px #aaaaaa + text-align:center + position:relative + cursor: pointer + + .inner + display:table-cell + vertical-align:middle + border-radius:5px + overflow:hidden + background-color: #cecece + diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 391769e9e..2614b5c1d 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -1,7 +1,17 @@ template(name="checklists") - h3 - i.fa.fa-check - | {{_ 'checklists'}} + .checklists-title + h3.card-details-item-title + i.fa.fa-check + | {{_ 'checklists'}} + if currentUser.isBoardMember + .material-toggle-switch(title="{{_ 'hide-checked-items'}}") + //span.toggle-switch-title + if hideCheckedItems + input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton" checked="checked") + else + input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton") + label.toggle-label(for="toggleHideCheckedItemsButton") + if toggleDeleteDialog.get .board-overlay#card-details-overlay +checklistDeleteDialog(checklist = checklistToDelete) @@ -15,9 +25,8 @@ template(name="checklists") +inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId) +addChecklistItemForm else - a.js-open-inlined-form + a.js-open-inlined-form(title="{{_ 'add-checklist'}}") i.fa.fa-plus - | {{_ 'add-checklist'}}... template(name="checklistDetail") .js-checklist.checklist @@ -31,6 +40,8 @@ template(name="checklistDetail") if canModifyCard h2.title.js-open-inlined-form.is-editable + if isMiniScreenOrShowDesktopDragHandles + span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}") +viewer = checklist.title else @@ -81,14 +92,16 @@ template(name="checklistItems") +inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist) +addChecklistItemForm else - a.add-checklist-item.js-open-inlined-form + a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}") i.fa.fa-plus - | {{_ 'add-checklist-item'}}... template(name='checklistItemDetail') - .js-checklist-item.checklist-item + .js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if hideCheckedItems}} invisible{{/if}}{{/if}}") if canModifyCard - .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") + .check-box-container + .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") + if isMiniScreenOrShowDesktopDragHandles + span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}") .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index c88fdd828..86f8c75f8 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -1,4 +1,4 @@ -const { calculateIndexData, enableClickOnTouch } = Utils; +const { calculateIndexData, capitalize } = Utils; function initSorting(items) { items.sortable({ @@ -6,7 +6,7 @@ function initSorting(items) { helper: 'clone', items: '.js-checklist-item:not(.placeholder)', connectWith: '.js-checklist-items', - appendTo: '.board-canvas', + appendTo: 'parent', distance: 7, placeholder: 'checklist-item placeholder', scroll: false, @@ -36,9 +36,6 @@ function initSorting(items) { checklistItem.move(checklistId, sortIndex.base); }, }); - - // ugly touch event hotfix - enableClickOnTouch('.js-checklist-item:not(.placeholder)'); } BlazeComponent.extendComponent({ @@ -54,14 +51,16 @@ BlazeComponent.extendComponent({ return Meteor.user() && Meteor.user().isBoardMember(); } - // Disable sorting if the current user is not a board member + // Disable sorting if the current user is not a board member or is a miniscreen self.autorun(() => { const $itemsDom = $(self.itemsDom); - if ($itemsDom.data('sortable')) { + if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) { $(self.itemsDom).sortable('option', 'disabled', !userIsMember()); - } - if ($itemsDom.data('sortable')) { - $(self.itemsDom).sortable('option', 'disabled', Utils.isMiniScreen()); + if (Utils.isMiniScreenOrShowDesktopDragHandles()) { + $(self.itemsDom).sortable({ + handle: 'span.fa.checklistitem-handle', + }); + } } }); }, @@ -112,7 +111,7 @@ BlazeComponent.extendComponent({ title, checklistId: checklist._id, cardId: checklist.cardId, - sort: checklist.itemCount(), + sort: Utils.calculateIndexData(checklist.lastItem()).base, }); } // We keep the form opened, empty it. @@ -177,6 +176,16 @@ BlazeComponent.extendComponent({ } }, + focusChecklistItem(event) { + // If a new checklist is created, pre-fill the title and select it. + const checklist = this.currentData().checklist; + if (!checklist) { + const textarea = event.target; + textarea.value = capitalize(TAPi18n.__('r-checklist')); + textarea.select(); + } + }, + events() { const events = { 'click .toggle-delete-checklist-dialog'(event) { @@ -185,6 +194,9 @@ BlazeComponent.extendComponent({ } this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get()); }, + 'click #toggleHideCheckedItemsButton'() { + Meteor.call('toggleHideCheckedItems'); + }, }; return [ @@ -196,12 +208,29 @@ BlazeComponent.extendComponent({ 'submit .js-edit-checklist-item': this.editChecklistItem, 'click .js-delete-checklist-item': this.deleteItem, 'click .confirm-checklist-delete': this.deleteChecklist, + 'focus .js-add-checklist-item': this.focusChecklistItem, keydown: this.pressKey, }, ]; }, }).register('checklists'); +Template.checklists.helpers({ + hideCheckedItems() { + const currentUser = Meteor.user(); + if (currentUser) return currentUser.hasHideCheckedItems(); + return false; + }, +}); + +Template.addChecklistItemForm.onRendered(() => { + autosize($('textarea.js-add-checklist-item')); +}); + +Template.editChecklistItemForm.onRendered(() => { + autosize($('textarea.js-edit-checklist-item')); +}); + Template.checklistDeleteDialog.onCreated(() => { const $cardDetails = this.$('.card-details'); this.scrollState = { @@ -237,6 +266,11 @@ Template.checklistItemDetail.helpers({ !Meteor.user().isWorker() ); }, + hideCheckedItems() { + const user = Meteor.user(); + if (user) return user.hasHideCheckedItems(); + return false; + }, }); BlazeComponent.extendComponent({ @@ -250,7 +284,7 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click .js-checklist-item .check-box': this.toggleItem, + 'click .js-checklist-item .check-box-container': this.toggleItem, }, ]; }, diff --git a/client/components/cards/checklists.styl b/client/components/cards/checklists.styl index 8ac37a15b..a356e3bd2 100644 --- a/client/components/cards/checklists.styl +++ b/client/components/cards/checklists.styl @@ -16,6 +16,10 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item &:hover color: inherit +.checklists-title + display: flex + justify-content: space-between + .checklist-title .checkbox float: left @@ -38,6 +42,11 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item .js-delete-checklist @extends .delete-text + span.fa.checklist-handle + padding-right: 20px + padding-top: 3px + float: left + .js-confirm-checklist-delete background-color: darken(white, 3%) @@ -99,6 +108,17 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item margin-top: 3px display: flex background: darken(white, 3%) + opacity: 1 + transition: height 0ms 400ms, opacity 400ms 0ms + height: auto + overflow: hidden + + &.is-checked.invisible + opacity: 0 + height: 0 + transition: height 0ms 0ms, opacity 600ms 0ms + margin-top: 0 + margin-bottom: 0 &.placeholder background: darken(white, 20%) @@ -113,6 +133,9 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item &:hover background-color: darken(white, 8%) + .check-box-container + padding-right: 10px; + .check-box margin: 0.1em 0 0 0; &.is-checked @@ -121,10 +144,10 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item .item-title flex: 1 - padding-left: 10px; &.is-checked color: #8c8c8c font-style: italic + text-decoration: line-through & .viewer p margin-bottom: 2px @@ -132,6 +155,10 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item word-wrap: break-word max-width: 420px + span.fa.checklistitem-handle + padding-top: 2px + padding-right: 10px; + .js-delete-checklist-item margin: 0 0 0.5em 1.33em @extends .delete-text diff --git a/client/components/cards/labels.styl b/client/components/cards/labels.styl index 9d7c75539..0e192e59f 100644 --- a/client/components/cards/labels.styl +++ b/client/components/cards/labels.styl @@ -44,9 +44,20 @@ align-items: center justify-content: center +.card-label-white + background-color: #ffffff + color: #000000 //Black text for better visibility + border: 1px solid #c0c0c0 + +.card-label-white:hover + color: #aaaaaa //grey text for better visibility + .card-label-green background-color: #3cb500 +.card-label-green:hover + color: #000000 //Black hover text for better visibility + .card-label-yellow background-color: #fad900 color: #000000 //Black text for better visibility @@ -158,6 +169,8 @@ .edit-labels-pop-over margin-bottom: 8px + .card-label .viewer p + margin: 0 .edit-labels-pop-over .shortcut display: inline-block diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index a895c0a38..925afec76 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -4,8 +4,8 @@ template(name="minicard") class="{{#if isLinkedBoard}}linked-board{{/if}}" class="minicard-{{colorClass}}") if isMiniScreen - //.handle - // .fa.fa-arrows + .handle + .fa.fa-arrows unless isMiniScreen if showDesktopDragHandles .handle @@ -74,20 +74,35 @@ template(name="minicard") +viewer = definition.name .minicard-custom-field-item - +viewer - = trueValue + if $eq definition.type "currency" + +viewer + = formattedCurrencyCustomFieldValue(definition) + else if $eq definition.type "date" + .date + +minicardCustomFieldDate + else if $eq definition.type "checkbox" + .materialCheckBox(class="{{#if value }}is-checked{{/if}}") + else if $eq definition.type "stringtemplate" + +viewer + = formattedStringtemplateCustomFieldValue(definition) + else + +viewer + = trueValue if getAssignees .minicard-assignees.js-minicard-assignees each getAssignees +userAvatar(userId=this) - hr if getMembers .minicard-members.js-minicard-members each getMembers +userAvatar(userId=this) + if showCreator + .minicard-creator + +userAvatar(userId=this.userId noRemove=true) + .badges unless currentUser.isNoComments if comments.count @@ -100,6 +115,17 @@ template(name="minicard") if getDescription .badge.badge-state-image-only(title=getDescription) span.badge-icon.fa.fa-align-left + if getVoteQuestion + .badge.badge-state-image-only(title=getVoteQuestion) + span.badge-icon.fa.fa-thumbs-up(class="{{#if voteState}}text-green{{/if}}") + span.badge-text {{ voteCountPositive }} + span.badge-icon.fa.fa-thumbs-down(class="{{#if $eq voteState false}}text-red{{/if}}") + span.badge-text {{ voteCountNegative }} + if getPokerQuestion + .badge.badge-state-image-only(title=getPokerQuestion) + span.badge-icon.fa.fa-check(class="{{#if pokerState}}text-green{{/if}}") + if expiredPoker + span.badge-text {{ getPokerEstimation }} if attachments.count .badge span.badge-icon.fa.fa-paperclip @@ -108,3 +134,12 @@ template(name="minicard") .badge(class="{{#if checklistFinished}}is-finished{{/if}}") span.badge-icon.fa.fa-check-square-o span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}} + if allSubtasks.count + .badge + span.badge-icon.fa.fa-sitemap + span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}} + //{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down + if currentBoard.allowsCardSortingByNumber + .badge + span.badge-icon.fa.fa-sort + span.badge-text {{ sort }} diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index da36b87f3..9ffe3609f 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -1,5 +1,3 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); // Template.cards.events({ // 'click .member': Popup.open('cardMember') // }); @@ -9,6 +7,48 @@ BlazeComponent.extendComponent({ return 'minicard'; }, + formattedCurrencyCustomFieldValue(definition) { + const customField = this.data() + .customFieldsWD() + .find(f => f._id === definition._id); + const customFieldTrueValue = + customField && customField.trueValue ? customField.trueValue : ''; + + const locale = TAPi18n.getLanguage(); + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: definition.settings.currencyCode, + }).format(customFieldTrueValue); + }, + + formattedStringtemplateCustomFieldValue(definition) { + const customField = this.data() + .customFieldsWD() + .find(f => f._id === definition._id); + + const customFieldTrueValue = + customField && customField.trueValue ? customField.trueValue : []; + + return customFieldTrueValue + .filter(value => !!value.trim()) + .map(value => + definition.settings.stringtemplateFormat.replace(/%\{value\}/gi, value), + ) + .join(definition.settings.stringtemplateSeparator ?? ''); + }, + + showCreator() { + if (this.data().board()) { + return ( + this.data().board.allowsCreator === null || + this.data().board().allowsCreator === undefined || + this.data().board().allowsCreator + ); + // return this.data().board().allowsCreator; + } + return false; + }, + events() { return [ { @@ -20,10 +60,10 @@ BlazeComponent.extendComponent({ }, { 'click .js-toggle-minicard-label-text'() { - if (cookies.has('hiddenMinicardLabelText')) { - cookies.remove('hiddenMinicardLabelText'); //true + if (window.localStorage.getItem('hiddenMinicardLabelText')) { + window.localStorage.removeItem('hiddenMinicardLabelText'); //true } else { - cookies.set('hiddenMinicardLabelText', 'true'); //true + window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true } }, }, @@ -36,7 +76,7 @@ Template.minicard.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; @@ -46,7 +86,7 @@ Template.minicard.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).hiddenMinicardLabelText; - } else if (cookies.has('hiddenMinicardLabelText')) { + } else if (window.localStorage.getItem('hiddenMinicardLabelText')) { return true; } else { return false; diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 7d72a5889..4e7bf53b7 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -87,7 +87,9 @@ width: 11px height: @width border-radius: 2px - margin-left: 3px + margin-right: 3px + margin-bottom: 3px + .minicard-custom-fields display:block; .minicard-custom-field @@ -161,15 +163,18 @@ line-height: 12px .minicard-members, - .minicard-assignees + .minicard-assignees, + .minicard-creator float: right - margin: 2px -8px 12px 0 + margin-left: 5px + margin-bottom: 4px .member float: right border-radius: 50% height: 28px width: @height + margin-bottom: 4px .assignee float: right @@ -178,7 +183,13 @@ width: @height + .badges - margin-top: 10px + margin-top: 5px + + .minicard-assignees + border-bottom: 1px solid red + + .minicard-creator + border-bottom: 1px solid green .minicard-members:empty, .minicard-assignees:empty @@ -299,3 +310,8 @@ minicard-color(background, color...) .minicard-indigo minicard-color(#4b0082, #ffffff) //White text for better visibility + +.text-red + color:red +.text-green + color:green diff --git a/client/components/cards/resultCard.jade b/client/components/cards/resultCard.jade new file mode 100644 index 000000000..cf001532b --- /dev/null +++ b/client/components/cards/resultCard.jade @@ -0,0 +1,44 @@ +template(name="resultCard") + .result-card-wrapper + a.minicard-wrapper.card-title(href=originRelativeUrl) + +minicard(this) + //= card.title + ul.result-card-context-list + li.result-card-context(title="{{_ 'board'}}") + .result-card-block-wrapper + if boardId + +viewer + = getBoard.title + else + .broken-cards-null + | NULL + if getBoard.archived + i.fa.fa-archive + li.result-card-context.result-card-context-separator + = ' ' + | {{_ 'context-separator'}} + = ' ' + li.result-card-context(title="{{_ 'swimlane'}}") + .result-card-block-wrapper + if swimlaneId + +viewer + = getSwimlane.title + else + .broken-cards-null + | NULL + if getSwimlane.archived + i.fa.fa-archive + li.result-card-context.result-card-context-separator + = ' ' + | {{_ 'context-separator'}} + = ' ' + li.result-card-context(title="{{_ 'list'}}") + .result-card-block-wrapper + if listId + +viewer + = getList.title + else + .broken-cards-null + | NULL + if getList.archived + i.fa.fa-archive diff --git a/client/components/cards/resultCard.js b/client/components/cards/resultCard.js new file mode 100644 index 000000000..3b5da12cf --- /dev/null +++ b/client/components/cards/resultCard.js @@ -0,0 +1,11 @@ +Template.resultCard.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +BlazeComponent.extendComponent({ + events() { + return [{}]; + }, +}).register('resultCard'); diff --git a/client/components/cards/resultCard.styl b/client/components/cards/resultCard.styl new file mode 100644 index 000000000..7aa94e90f --- /dev/null +++ b/client/components/cards/resultCard.styl @@ -0,0 +1,24 @@ +.result-card-list-wrapper + margin: 1rem + border-radius: 5px + padding: 1.5rem + padding-top: 0.75rem + display: inline-block + min-width: 250px + max-width: 350px + +.result-card-wrapper + margin-top: 0 + margin-bottom: 10px + +.result-card-context + display: inline-block + +.result-card-context-separator + font-weight: bold + +.result-card-context-list + margin-bottom: 0.7rem + +.result-card-block-wrapper + display: inline-block diff --git a/client/components/cards/subtasks.jade b/client/components/cards/subtasks.jade index df35bed31..8a0ef0b32 100644 --- a/client/components/cards/subtasks.jade +++ b/client/components/cards/subtasks.jade @@ -1,11 +1,11 @@ template(name="subtasks") - h3 + h3.card-details-item-title i.fa.fa-sitemap | {{_ 'subtasks'}} - if toggleDeleteDialog.get - .board-overlay#card-details-overlay - +subtaskDeleteDialog(subtask = subtaskToDelete) - + if currentUser.isBoardAdmin + if toggleDeleteDialog.get + .board-overlay#card-details-overlay + +subtaskDeleteDialog(subtask = subtaskToDelete) .card-subtasks-items each subtask in currentCard.subtasks @@ -15,9 +15,8 @@ template(name="subtasks") +inlinedForm(autoclose=false classNames="js-add-subtask" cardId = cardId) +addSubtaskItemForm else - a.js-open-inlined-form + a.js-open-inlined-form(title="{{_ 'add-subtask'}}") i.fa.fa-plus - | {{_ 'add-subtask'}}... template(name="subtaskDetail") .js-subtasks.subtask @@ -28,7 +27,8 @@ template(name="subtaskDetail") span a.js-view-subtask(title="{{ subtask.title }}") {{_ "view-it"}} if canModifyCard - a.js-delete-subtask.toggle-delete-subtask-dialog {{_ "delete"}}... + if currentUser.isBoardAdmin + a.js-delete-subtask.toggle-delete-subtask-dialog {{_ "delete"}}... if canModifyCard h2.title.js-open-inlined-form.is-editable @@ -68,7 +68,8 @@ template(name="editSubtaskItemForm") a.fa.fa-times-thin.js-close-inlined-form span(title=createdAt) {{ moment createdAt }} if canModifyCard - a.js-delete-subtask-item {{_ "delete"}}... + if currentUser.isBoardAdmin + a.js-delete-subtask-item {{_ "delete"}}... template(name="subtasksItems") .subtasks-items.js-subtasks-items diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js index cdc227fc2..4cd15c114 100644 --- a/client/components/cards/subtasks.js +++ b/client/components/cards/subtasks.js @@ -22,11 +22,20 @@ BlazeComponent.extendComponent({ const listId = targetBoard.getDefaultSubtasksListId(); //Get the full swimlane data for the parent task. - const parentSwimlane = Swimlanes.findOne({boardId: crtBoard._id, _id: card.swimlaneId}); + const parentSwimlane = Swimlanes.findOne({ + boardId: crtBoard._id, + _id: card.swimlaneId, + }); //find the swimlane of the same name in the target board. - const targetSwimlane = Swimlanes.findOne({boardId: targetBoard._id, title: parentSwimlane.title}); + const targetSwimlane = Swimlanes.findOne({ + boardId: targetBoard._id, + title: parentSwimlane.title, + }); //If no swimlane with a matching title exists in the target board, fall back to the default swimlane. - const swimlaneId = targetSwimlane === undefined ? targetBoard.getDefaultSwimline()._id : targetSwimlane._id; + const swimlaneId = + targetSwimlane === undefined + ? targetBoard.getDefaultSwimline()._id + : targetSwimlane._id; if (title) { const _id = Cards.insert({ diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index bfae2e7cd..d41ea4e62 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -86,7 +86,7 @@ select margin-bottom: 8px &.inline - width: 100% + width: 100% option[disabled] color: #8c8c8c @@ -242,11 +242,11 @@ textarea margin: 3px 4px // Material Design checkboxes -[type="checkbox"]:not(:checked), -[type="checkbox"]:checked - position: absolute - left: -9999px - visibility: hidden + [type="checkbox"]:not(:checked), + [type="checkbox"]:checked + position: absolute + left: -9999px + visibility: hidden .materialCheckBox position: relative diff --git a/client/components/import/csvMembersMapper.js b/client/components/import/csvMembersMapper.js new file mode 100644 index 000000000..76d3366f7 --- /dev/null +++ b/client/components/import/csvMembersMapper.js @@ -0,0 +1,37 @@ +export function csvGetMembersToMap(data) { + // we will work on the list itself (an ordered array of objects) when a + // mapping is done, we add a 'wekan' field to the object representing the + // imported member + + const membersToMap = []; + const importedMembers = []; + let membersIndex; + + for (let i = 0; i < data[0].length; i++) { + if (data[0][i].toLowerCase() === 'members') { + membersIndex = i; + } + } + + for (let i = 1; i < data.length; i++) { + if (data[i][membersIndex]) { + for (const importedMember of data[i][membersIndex].split(' ')) { + if (importedMember && importedMembers.indexOf(importedMember) === -1) { + importedMembers.push(importedMember); + } + } + } + } + + for (let importedMember of importedMembers) { + importedMember = { + username: importedMember, + id: importedMember, + }; + const wekanUser = Users.findOne({ username: importedMember.username }); + if (wekanUser) importedMember.wekanId = wekanUser._id; + membersToMap.push(importedMember); + } + + return membersToMap; +} diff --git a/client/components/import/import.jade b/client/components/import/import.jade index 5b52f4170..aac0aae15 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -13,41 +13,43 @@ template(name="import") template(name="importTextarea") form p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}} - textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus) + textarea.js-import-json(id='import-textarea' placeholder="{{_ importPlaceHolder}}" autofocus) | {{jsonText}} - if isSandstorm - h1.warning {{_ 'import-sandstorm-backup-warning'}} - p.warning {{_ 'import-sandstorm-warning'}} input.primary.wide(type="submit" value="{{_ 'import'}}") template(name="importMapMembers") h2 {{_ 'import-map-members'}} - .map-members - p {{_ 'import-members-map'}} - .mapping-list - each members - a.mapping-item.js-select-member(class="{{#if wekanId}}filled{{/if}}") - .profile-source - .full-name= fullName - .username - | ({{username}}) - .wekan - if wekanId - +userAvatar(userId=wekanId) - else - a.member.add-member - i.fa.fa-plus - //- - Due to the way the flewbox layout is working, we need to set some - invisible items so that the last row items have a consistent width. - See http://jsfiddle.net/Ln4h3c4n/ for an minimal example of the issue. - .mapping-item.ghost-item - .mapping-item.ghost-item - .mapping-item.ghost-item - .mapping-item.ghost-item - .mapping-item.ghost-item - form - input.primary.wide(type="submit" value="{{_ 'done'}}") + if usersLoaded.get + .map-members + p {{_ 'import-members-map'}} + p.import-members-map-note + | {{_ 'import-members-map-note' }} + .mapping-list + each members + a.mapping-item.js-select-member(class="{{#if wekanId}}filled{{/if}}") + .profile-source + .full-name= fullName + .username + | ({{username}}) + .wekan + if wekanId + +userAvatar(userId=wekanId) + else + a.member.add-member + i.fa.fa-plus + //- + Due to the way the flewbox layout is working, we need to set some + invisible items so that the last row items have a consistent width. + See http://jsfiddle.net/Ln4h3c4n/ for an minimal example of the issue. + .mapping-item.ghost-item + .mapping-item.ghost-item + .mapping-item.ghost-item + .mapping-item.ghost-item + .mapping-item.ghost-item + form + input.primary.wide(type="submit" value="{{_ 'done'}}") + else + +spinner template(name="importMapMembersAddPopup") .select-member diff --git a/client/components/import/import.js b/client/components/import/import.js index 6368885bd..3176a68b0 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,5 +1,8 @@ -import trelloMembersMapper from './trelloMembersMapper'; -import wekanMembersMapper from './wekanMembersMapper'; +import { trelloGetMembersToMap } from './trelloMembersMapper'; +import { wekanGetMembersToMap } from './wekanMembersMapper'; +import { csvGetMembersToMap } from './csvMembersMapper'; + +const Papa = require('papaparse'); BlazeComponent.extendComponent({ title() { @@ -30,20 +33,30 @@ BlazeComponent.extendComponent({ } }, - importData(evt) { + importData(evt, dataSource) { evt.preventDefault(); - const dataJson = this.find('.js-import-json').value; - try { - const dataObject = JSON.parse(dataJson); - this.setError(''); - this.importedData.set(dataObject); - const membersToMap = this._prepareAdditionalData(dataObject); - // store members data and mapping in Session - // (we go deep and 2-way, so storing in data context is not a viable option) + const input = this.find('.js-import-json').value; + if (dataSource === 'csv') { + const csv = input.indexOf('\t') > 0 ? input.replace(/(\t)/g, ',') : input; + const ret = Papa.parse(csv); + if (ret && ret.data && ret.data.length) this.importedData.set(ret.data); + else throw new Meteor.Error('error-csv-schema'); + const membersToMap = this._prepareAdditionalData(ret.data); this.membersToMap.set(membersToMap); this.nextStep(); - } catch (e) { - this.setError('error-json-malformed'); + } else { + try { + const dataObject = JSON.parse(input); + this.setError(''); + this.importedData.set(dataObject); + const membersToMap = this._prepareAdditionalData(dataObject); + // store members data and mapping in Session + // (we go deep and 2-way, so storing in data context is not a viable option) + this.membersToMap.set(membersToMap); + this.nextStep(); + } catch (e) { + this.setError('error-json-malformed'); + } } }, @@ -86,10 +99,13 @@ BlazeComponent.extendComponent({ let membersToMap; switch (importSource) { case 'trello': - membersToMap = trelloMembersMapper.getMembersToMap(dataObject); + membersToMap = trelloGetMembersToMap(dataObject); break; case 'wekan': - membersToMap = wekanMembersMapper.getMembersToMap(dataObject); + membersToMap = wekanGetMembersToMap(dataObject); + break; + case 'csv': + membersToMap = csvGetMembersToMap(dataObject); break; } return membersToMap; @@ -109,11 +125,23 @@ BlazeComponent.extendComponent({ return `import-board-instruction-${Session.get('importSource')}`; }, + importPlaceHolder() { + const importSource = Session.get('importSource'); + if (importSource === 'csv') { + return 'import-csv-placeholder'; + } else { + return 'import-json-placeholder'; + } + }, + events() { return [ { submit(evt) { - return this.parentComponent().importData(evt); + return this.parentComponent().importData( + evt, + Session.get('importSource'), + ); }, }, ]; @@ -122,14 +150,42 @@ BlazeComponent.extendComponent({ BlazeComponent.extendComponent({ onCreated() { + this.usersLoaded = new ReactiveVar(false); + this.autorun(() => { - this.parentComponent() - .membersToMap.get() - .forEach(({ wekanId }) => { - if (wekanId) { - this.subscribe('user-miniprofile', wekanId); + const handle = this.subscribe( + 'user-miniprofile', + this.members().map(member => { + return member.username; + }), + ); + Tracker.nonreactive(() => { + Tracker.autorun(() => { + if ( + handle.ready() && + !this.usersLoaded.get() && + this.members().length + ) { + this._refreshMembers( + this.members().map(member => { + if (!member.wekanId) { + let user = Users.findOne({ username: member.username }); + if (!user) { + user = Users.findOne({ importUsernames: member.username }); + } + if (user) { + // eslint-disable-next-line no-console + // console.log('found username:', user.username); + member.wekanId = user._id; + } + } + return member; + }), + ); } + this.usersLoaded.set(handle.ready()); }); + }); }); }, diff --git a/client/components/import/import.styl b/client/components/import/import.styl index a0de74bb0..d34048495 100644 --- a/client/components/import/import.styl +++ b/client/components/import/import.styl @@ -47,3 +47,7 @@ a.show-mapping text-decoration underline + +.import-members-map-note + font-size: 90% + font-weight: bold diff --git a/client/components/import/trelloMembersMapper.js b/client/components/import/trelloMembersMapper.js index b5d59d931..9e72a698d 100644 --- a/client/components/import/trelloMembersMapper.js +++ b/client/components/import/trelloMembersMapper.js @@ -1,4 +1,4 @@ -export function getMembersToMap(data) { +export function trelloGetMembersToMap(data) { // we will work on the list itself (an ordered array of objects) when a // mapping is done, we add a 'wekan' field to the object representing the // imported member diff --git a/client/components/import/wekanMembersMapper.js b/client/components/import/wekanMembersMapper.js index 8db3aacd7..66b11bea4 100644 --- a/client/components/import/wekanMembersMapper.js +++ b/client/components/import/wekanMembersMapper.js @@ -1,4 +1,4 @@ -export function getMembersToMap(data) { +export function wekanGetMembersToMap(data) { // we will work on the list itself (an ordered array of objects) when a // mapping is done, we add a 'wekan' field to the object representing the // imported member diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 8574caf76..1cfbaff9a 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -1,6 +1,4 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); -const { calculateIndex, enableClickOnTouch } = Utils; +const { calculateIndex } = Utils; BlazeComponent.extendComponent({ // Proxy @@ -74,18 +72,16 @@ BlazeComponent.extendComponent({ const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards); const listId = Blaze.getData(ui.item.parents('.list').get(0))._id; const currentBoard = Boards.findOne(Session.get('currentBoard')); - let swimlaneId = ''; + const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id; + let targetSwimlaneId = null; + + // only set a new swimelane ID if the swimlanes view is active if ( Utils.boardView() === 'board-view-swimlanes' || currentBoard.isTemplatesBoard() ) - swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id; - else if ( - Utils.boardView() === 'board-view-lists' || - Utils.boardView() === 'board-view-cal' || - !Utils.boardView - ) - swimlaneId = currentBoard.getDefaultSwimline()._id; + targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0)) + ._id; // Normally the jquery-ui sortable library moves the dragged DOM element // to its new position, which disrupts Blaze reactive updates mechanism @@ -98,9 +94,12 @@ BlazeComponent.extendComponent({ if (MultiSelection.isActive()) { Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => { + const newSwimlaneId = targetSwimlaneId + ? targetSwimlaneId + : card.swimlaneId || defaultSwimlaneId; card.move( currentBoard._id, - swimlaneId, + newSwimlaneId, listId, sortIndex.base + i * sortIndex.increment, ); @@ -108,28 +107,28 @@ BlazeComponent.extendComponent({ } else { const cardDomElement = ui.item.get(0); const card = Blaze.getData(cardDomElement); - card.move(currentBoard._id, swimlaneId, listId, sortIndex.base); + const newSwimlaneId = targetSwimlaneId + ? targetSwimlaneId + : card.swimlaneId || defaultSwimlaneId; + card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base); } boardComponent.setIsDragging(false); }, }); - // ugly touch event hotfix - enableClickOnTouch(itemsSelector); - this.autorun(() => { let showDesktopDragHandles = false; currentUser = Meteor.user(); if (currentUser) { showDesktopDragHandles = (currentUser.profile || {}) .showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { showDesktopDragHandles = true; } else { showDesktopDragHandles = false; } - if (!Utils.isMiniScreen() && showDesktopDragHandles) { + if (Utils.isMiniScreen() || showDesktopDragHandles) { $cards.sortable({ handle: '.handle', }); @@ -139,27 +138,16 @@ BlazeComponent.extendComponent({ }); } - if ($cards.data('sortable')) { + if ($cards.data('uiSortable') || $cards.data('sortable')) { $cards.sortable( 'option', 'disabled', - // Disable drag-dropping when user is not member/is miniscreen + // Disable drag-dropping when user is not member !userIsMember(), // Not disable drag-dropping while in multi-selection mode // MultiSelection.isActive() || !userIsMember(), ); } - - if ($cards.data('sortable')) { - $cards.sortable( - 'option', - 'disabled', - // Disable drag-dropping when user is not member/is miniscreen - Utils.isMiniScreen(), - // Not disable drag-dropping while in multi-selection mode - // MultiSelection.isActive() || !userIsMember(), - ); - } }); // We want to re-run this function any time a card is added. @@ -195,7 +183,7 @@ Template.list.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl index 27cf678c4..368394ae1 100644 --- a/client/components/lists/list.styl +++ b/client/components/lists/list.styl @@ -43,9 +43,6 @@ background: white margin: -3px 0 8px -.list-header-card-count - height: 35px - .list-header-add flex: 0 0 auto padding: 20px 12px 4px @@ -60,6 +57,9 @@ background-color: #e4e4e4; border-bottom: 6px solid #e4e4e4; + &.list-header-card-count + min-height: 35px + height: auto &.ui-sortable-handle cursor: grab @@ -120,9 +120,6 @@ form margin-bottom: 9px - .ps-scrollbar-y-rail - transform: translateX(2px) - .open-minicard-composer border-radius: 2px color: #8c8c8c @@ -183,7 +180,8 @@ border-bottom: 1px solid darken(white, 20%) .list - display: block + display: contents + flex-basis: auto width: 100% border-left: 0px &:first-child diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index 2f1752053..b286347a9 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -1,11 +1,11 @@ template(name="listBody") - .list-body.js-perfect-scrollbar + .list-body .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}") if cards.count +inlinedForm(autoclose=false position="top") +addCardForm(listId=_id position="top") each (cardsWithLimit (idOrNull ../../_id)) - a.minicard-wrapper.js-minicard(href=absoluteUrl + a.minicard-wrapper.js-minicard(href=originRelativeUrl class="{{#if cardIsSelected}}is-selected{{/if}}" class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") if MultiSelection.isActive @@ -19,19 +19,14 @@ template(name="listBody") +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") else - a.open-minicard-composer.js-card-composer.js-open-inlined-form + a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}") i.fa.fa-plus - | {{_ 'add-card'}} template(name="spinnerList") - .sk-spinner.sk-spinner-wave.sk-spinner-list( - class=currentBoard.colorClass + .sk-spinner.sk-spinner-list( + class="{{currentBoard.colorClass}} {{getSkSpinnerName}}" id="showMoreResults") - .sk-rect1 - .sk-rect2 - .sk-rect3 - .sk-rect4 - .sk-rect5 + +spinnerRaw template(name="addCardForm") .minicard.minicard-composer.js-composer @@ -105,8 +100,10 @@ template(name="searchElementPopup") each boards option(value="{{_id}}") {{title}} form.js-search-term-form + label + | {{_ 'template'}} input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto") - .list-body.js-perfect-scrollbar.search-card-results + .list-body.search-card-results .minicards.clearfix.js-minicards if isBoardTemplateSearch each results diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 03f88f638..f56ba6ba1 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,3 +1,5 @@ +import { Spinner } from '/client/lib/spinner'; + const subManager = new SubsManager(); const InfiniteScrollIter = 10; @@ -8,7 +10,7 @@ BlazeComponent.extendComponent({ }, mixins() { - return [Mixins.PerfectScrollbar]; + return []; }, openForm(options) { @@ -77,7 +79,7 @@ BlazeComponent.extendComponent({ else if ( Utils.boardView() === 'board-view-lists' || Utils.boardView() === 'board-view-cal' || - !Utils.boardView + !Utils.boardView() ) swimlaneId = board.getDefaultSwimline()._id; @@ -116,8 +118,6 @@ BlazeComponent.extendComponent({ if (position === 'bottom') { this.scrollToBottom(); } - - formComponent.reset(); } }, @@ -168,13 +168,16 @@ BlazeComponent.extendComponent({ cardsWithLimit(swimlaneId) { const limit = this.cardlimit.get(); + const defaultSort = { sort: 1 }; + const sortBy = Session.get('sortBy') ? Session.get('sortBy') : defaultSort; const selector = { listId: this.currentData()._id, archived: false, }; if (swimlaneId) selector.swimlaneId = swimlaneId; return Cards.find(Filter.mongoSelector(selector), { - sort: ['sort'], + // sort: ['sort'], + sort: sortBy, limit, }); }, @@ -239,7 +242,7 @@ BlazeComponent.extendComponent({ .customFields() .fetch(), function(field) { - if (field.automaticallyOnCard) + if (field.automaticallyOnCard || field.alwaysOnCard) arr.push({ _id: field._id, value: null }); }, ); @@ -411,7 +414,7 @@ BlazeComponent.extendComponent({ type: 'board', }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -523,7 +526,7 @@ BlazeComponent.extendComponent({ BlazeComponent.extendComponent({ mixins() { - return [Mixins.PerfectScrollbar]; + return []; }, onCreated() { @@ -549,7 +552,7 @@ BlazeComponent.extendComponent({ board = Boards.findOne((Meteor.user().profile || {}).templatesBoardId); } else { // Prefetch first non-current board id - board = Boards.findOne({ + board = Boards.find({ archived: false, 'members.userId': Meteor.userId(), _id: { @@ -597,7 +600,7 @@ BlazeComponent.extendComponent({ type: 'board', }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -658,10 +661,7 @@ BlazeComponent.extendComponent({ _id = element.copy(this.boardId, this.swimlaneId, this.listId); // 1.B Linked card } else { - delete element._id; - element.type = 'cardType-linkedCard'; - element.linkedId = element.linkedId || element._id; - _id = Cards.insert(element); + _id = element.link(this.boardId, this.swimlaneId, this.listId); } Filter.addException(_id); // List insertion @@ -675,15 +675,21 @@ BlazeComponent.extendComponent({ element.sort = Boards.findOne(this.boardId) .swimlanes() .count(); - element.type = 'swimlalne'; + element.type = 'swimlane'; _id = element.copy(this.boardId); } else if (this.isBoardTemplateSearch) { - board = Boards.findOne(element.linkedId); - board.sort = Boards.find({ archived: false }).count(); - board.type = 'board'; - board.title = element.title; - delete board.slug; - _id = board.copy(); + Meteor.call( + 'copyBoard', + element.linkedId, + { + sort: Boards.find({ archived: false }).count(), + type: 'board', + title: element.title, + }, + (err, data) => { + _id = data; + }, + ); } Popup.close(); }, @@ -692,7 +698,7 @@ BlazeComponent.extendComponent({ }, }).register('searchElementPopup'); -BlazeComponent.extendComponent({ +(class extends Spinner { onCreated() { this.cardlimit = this.parentComponent().cardlimit; @@ -720,11 +726,11 @@ BlazeComponent.extendComponent({ .parentComponent() .data()._id; } - }, + } onRendered() { this.spinner = this.find('.sk-spinner-list'); - this.container = this.$(this.spinner).parents('.js-perfect-scrollbar')[0]; + this.container = this.$(this.spinner).parents('.list-body')[0]; $(this.container).on( `scroll.spinner_${this.swimlaneId}_${this.listId}`, @@ -735,47 +741,58 @@ BlazeComponent.extendComponent({ ); this.updateList(); - }, + } onDestroyed() { $(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`); $(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`); - }, + } + + checkIdleTime() { + return window.requestIdleCallback || + function(handler) { + const startTime = Date.now(); + return setTimeout(function() { + handler({ + didTimeout: false, + timeRemaining() { + return Math.max(0, 50.0 - (Date.now() - startTime)); + }, + }); + }, 1); + }; + } updateList() { // Use fallback when requestIdleCallback is not available on iOS and Safari // https://www.afasterweb.com/2017/11/20/utilizing-idle-moments/ - checkIdleTime = - window.requestIdleCallback || - function(handler) { - const startTime = Date.now(); - return setTimeout(function() { - handler({ - didTimeout: false, - timeRemaining() { - return Math.max(0, 50.0 - (Date.now() - startTime)); - }, - }); - }, 1); - }; if (this.spinnerInView()) { this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); - checkIdleTime(() => this.updateList()); + this.checkIdleTime(() => this.updateList()); } - }, + } spinnerInView() { - const parentViewHeight = this.container.clientHeight; - const bottomViewPosition = this.container.scrollTop + parentViewHeight; - - const threshold = this.spinner.offsetTop; - // spinner deleted if (!this.spinner.offsetTop) { return false; } - return bottomViewPosition > threshold; - }, -}).register('spinnerList'); + const parentViewHeight = this.container.clientHeight; + const bottomViewPosition = this.container.scrollTop + parentViewHeight; + + let spinnerOffsetTop = this.spinner.offsetTop; + + const addCard = $(this.container).find("a.open-minicard-composer").first()[0]; + if (addCard !== undefined) { + spinnerOffsetTop -= addCard.clientHeight; + } + + return bottomViewPosition > spinnerOffsetTop; + } + + getSkSpinnerName() { + return "sk-spinner-" + super.getSpinnerName().toLowerCase(); + } +}.register('spinnerList')); diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 182fee9e7..9286ab122 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -1,7 +1,7 @@ template(name="listHeader") .list-header.js-list-header( class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}" - class="{{#if colorClass}}list-header-{{colorClass}}{{/if}}") + class=colorClass) +inlinedForm +editListTitleForm else @@ -15,7 +15,7 @@ template(name="listHeader") = title if wipLimit.enabled | ( - span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}} + span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}} |/#{wipLimit.value}) if showCardsCountForList cards.count @@ -28,12 +28,11 @@ template(name="listHeader") div.list-header-menu unless currentUser.isCommentOnly if canSeeAddCard - a.js-add-card.fa.fa-plus.list-header-plus-icon - a.fa.fa-navicon.js-open-list-menu - //a.list-header-handle.handle.fa.fa-arrows.js-list-handle + a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}") + a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") else a.list-header-menu-icon.fa.fa-angle-right.js-select-list - //a.list-header-handle.handle.fa.fa-arrows.js-list-handle + a.list-header-handle.handle.fa.fa-arrows.js-list-handle else if currentUser.isBoardMember if isWatching i.list-header-watch-icon.fa.fa-eye @@ -42,10 +41,11 @@ template(name="listHeader") //if isBoardAdmin // a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}") if canSeeAddCard - a.js-add-card.fa.fa-plus.list-header-plus-icon - a.fa.fa-navicon.js-open-list-menu - if showDesktopDragHandles - a.list-header-handle.handle.fa.fa-arrows.js-list-handle + a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}") + a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") + if currentUser.isBoardAdmin + if showDesktopDragHandles + a.list-header-handle.handle.fa.fa-arrows.js-list-handle template(name="editListTitleForm") .list-composer @@ -116,8 +116,9 @@ template(name="listMorePopup") input.inline-input(type="text" readonly value="{{ rootUrl }}") | {{_ 'added'}} span.date(title=list.createdAt) {{ moment createdAt 'LLL' }} - unless currentUser.isWorker - a.js-delete {{_ 'delete'}} + //unless currentUser.isWorker + // if currentUser.isBoardAdmin + // a.js-delete {{_ 'delete'}} template(name="listDeletePopup") p {{_ "list-delete-pop"}} @@ -152,7 +153,7 @@ template(name="setListColorPopup") form.edit-label .palette-colors: each colors // note: we use the swimlane palette to have more than just the border - span.card-label.palette-color.js-palette-color(class="swimlane-{{color}}") + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") if(isSelected color) i.fa.fa-check button.primary.confirm.js-submit {{_ 'save'}} diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 46dbd7487..8afb35fba 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -1,5 +1,3 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); let listsColors; Meteor.startup(() => { listsColors = Lists.simpleSchema()._schema.color.allowedValues; @@ -74,9 +72,17 @@ BlazeComponent.extendComponent({ ); }, + exceededWipLimit() { + const list = Template.currentData(); + return ( + list.getWipLimit('enabled') && + list.getWipLimit('value') < list.cards().count() + ); + }, + showCardsCountForList(count) { const limit = this.limitToShowCardsCount(); - return limit > 0 && count > limit; + return limit >= 0 && count >= limit; }, events() { @@ -106,11 +112,15 @@ BlazeComponent.extendComponent({ }).register('listHeader'); Template.listHeader.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + showDesktopDragHandles() { currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; @@ -119,6 +129,10 @@ Template.listHeader.helpers({ }); Template.listActionPopup.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + isWipLimitEnabled() { return Template.currentData().getWipLimit('enabled'); }, @@ -223,12 +237,45 @@ BlazeComponent.extendComponent({ Template.listMorePopup.events({ 'click .js-delete': Popup.afterConfirm('listDelete', function() { Popup.close(); - this.allCards().map(card => Cards.remove(card._id)); - Lists.remove(this._id); + // TODO how can we avoid the fetch call? + const allCards = this.allCards().fetch(); + const allCardIds = _.pluck(allCards, '_id'); + // it's okay if the linked cards are on the same list + if ( + Cards.find({ + $and: [ + { listId: { $ne: this._id } }, + { linkedId: { $in: allCardIds } }, + ], + }).count() === 0 + ) { + allCardIds.map(_id => Cards.remove(_id)); + Lists.remove(this._id); + } else { + // TODO: Figure out more informative message. + // Popup with a hint that the list cannot be deleted as there are + // linked cards. We can adapt the query above so we can list the linked + // cards. + // Related: + // client/components/cards/cardDetails.js about line 969 + // https://github.com/wekan/wekan/issues/2785 + const message = `${TAPi18n.__( + 'delete-linked-cards-before-this-list', + )} linkedId: ${ + this._id + } at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`; + alert(message); + } Utils.goBoardId(this.boardId); }), }); +Template.listHeader.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, +}); + BlazeComponent.extendComponent({ onCreated() { this.currentList = this.currentData(); @@ -240,7 +287,11 @@ BlazeComponent.extendComponent({ }, isSelected(color) { - return this.currentColor.get() === color; + if (this.currentColor.get() === null) { + return color === 'white'; + } else { + return this.currentColor.get() === color; + } }, events() { diff --git a/client/components/main/brokenCards.jade b/client/components/main/brokenCards.jade new file mode 100644 index 000000000..9d5828905 --- /dev/null +++ b/client/components/main/brokenCards.jade @@ -0,0 +1,17 @@ +template(name="brokenCardsHeaderBar") + h1 + | {{_ 'broken-cards'}} + +template(name="brokenCards") + if currentUser + if searching.get + +spinner + else if hasResults.get + .global-search-results-list-wrapper + if hasQueryErrors.get + div + each msg in errorMessages + span.global-search-error-messages + = msg + else + +resultsPaged(this) diff --git a/client/components/main/brokenCards.js b/client/components/main/brokenCards.js new file mode 100644 index 000000000..17d30f37c --- /dev/null +++ b/client/components/main/brokenCards.js @@ -0,0 +1,18 @@ +import { CardSearchPagedComponent } from '../../lib/cardSearch'; + +BlazeComponent.extendComponent({}).register('brokenCardsHeaderBar'); + +Template.brokenCards.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +class BrokenCardsComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + + Meteor.subscribe('brokenCards', this.sessionId); + } +} +BrokenCardsComponent.register('brokenCards'); diff --git a/client/components/main/brokenCards.styl b/client/components/main/brokenCards.styl new file mode 100644 index 000000000..d9603cecb --- /dev/null +++ b/client/components/main/brokenCards.styl @@ -0,0 +1,31 @@ +.broken-cards-card-wrapper + margin-top: 0 + margin-bottom: 10px + border-width: 3px !important + border-color: grey !important + border-style: solid + border-radius: 5px + padding: 1.5rem + background-color: white + +.broken-cards-wrapper + max-width: 500px + margin-right: auto + margin-left: auto + +.broken-cards-card-title + font-weight: bold + //padding: 10px + +.broken-cards-context + display: inline-block + +.broken-cards-context-separator + font-weight: bold + +.broken-cards-context-list + //margin-bottom: 0.7rem + +.broken-cards-null + color: darkred + font-style: italic diff --git a/client/components/main/dueCards.jade b/client/components/main/dueCards.jade new file mode 100644 index 000000000..a1970839e --- /dev/null +++ b/client/components/main/dueCards.jade @@ -0,0 +1,57 @@ +template(name="dueCardsHeaderBar") + if currentUser + h1 + i.fa.fa-calendar + | {{_ 'dueCards-title'}} + + .board-header-btns.left + a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}") + i.fa.fa-caret-down + if $eq dueCardsView 'me' + i.fa.fa-user + | {{_ 'dueCardsViewChange-choice-me'}} + if $eq dueCardsView 'all' + i.fa.fa-users + | {{_ 'dueCardsViewChange-choice-all'}} + +template(name="dueCardsModalTitle") + if currentUser + h2 + i.fa.fa-keyboard-o + | {{_ 'dueCards-title'}} + +template(name="dueCards") + if currentUser + if searching.get + +spinner + else if hasResults.get + .global-search-results-list-wrapper + if hasQueryErrors.get + div + each msg in errorMessages + span.global-search-error-messages + = msg + else + +resultsPaged(this) + +template(name="dueCardsViewChangePopup") + if currentUser + ul.pop-over-list + li + with "dueCardsViewChange-choice-me" + a.js-due-cards-view-me + i.fa.fa-user.colorful + | {{_ 'dueCardsViewChange-choice-me'}} + if $eq Utils.dueCardsView "me" + i.fa.fa-check + hr + li + with "dueCardsViewChange-choice-all" + a.js-due-cards-view-all + i.fa.fa-users.colorful + | {{_ 'dueCardsViewChange-choice-all'}} + span.sub-name + +viewer + | {{_ 'dueCardsViewChange-choice-all-description' }} + if $eq Utils.dueCardsView "all" + i.fa.fa-check diff --git a/client/components/main/dueCards.js b/client/components/main/dueCards.js new file mode 100644 index 000000000..4841ddb27 --- /dev/null +++ b/client/components/main/dueCards.js @@ -0,0 +1,111 @@ +import { CardSearchPagedComponent } from '../../lib/cardSearch'; +import { + OPERATOR_HAS, + OPERATOR_SORT, + OPERATOR_USER, + ORDER_ASCENDING, + PREDICATE_DUE_AT, +} from '../../../config/search-const'; +import { QueryParams } from '../../../config/query-classes'; + +// const subManager = new SubsManager(); + +BlazeComponent.extendComponent({ + dueCardsView() { + // eslint-disable-next-line no-console + // console.log('sort:', Utils.dueCardsView()); + return Utils.dueCardsView(); + }, + + events() { + return [ + { + 'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'), + }, + ]; + }, +}).register('dueCardsHeaderBar'); + +Template.dueCards.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-due-cards-view-me'() { + Utils.setDueCardsView('me'); + Popup.close(); + }, + + 'click .js-due-cards-view-all'() { + Utils.setDueCardsView('all'); + Popup.close(); + }, + }, + ]; + }, +}).register('dueCardsViewChangePopup'); + +class DueCardsComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + + const queryParams = new QueryParams(); + queryParams.addPredicate(OPERATOR_HAS, { + field: PREDICATE_DUE_AT, + exists: true, + }); + // queryParams[OPERATOR_LIMIT] = 5; + queryParams.addPredicate(OPERATOR_SORT, { + name: PREDICATE_DUE_AT, + order: ORDER_ASCENDING, + }); + + if (Utils.dueCardsView() !== 'all') { + queryParams.addPredicate(OPERATOR_USER, Meteor.user().username); + } + + this.runGlobalSearch(queryParams); + } + + dueCardsView() { + // eslint-disable-next-line no-console + //console.log('sort:', Utils.dueCardsView()); + return Utils.dueCardsView(); + } + + sortByBoard() { + return this.dueCardsView() === 'board'; + } + + dueCardsList() { + const results = this.getResults(); + console.log('results:', results); + const cards = []; + if (results) { + results.forEach(card => { + cards.push(card); + }); + } + + cards.sort((a, b) => { + const x = a.dueAt === null ? new Date('2100-12-31') : a.dueAt; + const y = b.dueAt === null ? new Date('2100-12-31') : b.dueAt; + + if (x > y) return 1; + else if (x < y) return -1; + + return 0; + }); + + // eslint-disable-next-line no-console + console.log('cards:', cards); + return cards; + } +} + +DueCardsComponent.register('dueCards'); diff --git a/client/components/main/dueCards.styl b/client/components/main/dueCards.styl new file mode 100644 index 000000000..885555225 --- /dev/null +++ b/client/components/main/dueCards.styl @@ -0,0 +1,4 @@ +.due-cards-dueat-list-wrapper + max-width: 500px + margin-right: auto + margin-left: auto diff --git a/client/components/main/editor.js b/client/components/main/editor.js old mode 100755 new mode 100644 index 081c6521c..7437c2306 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -49,8 +49,8 @@ Template.editor.onRendered(() => { ['para', ['ul', 'ol', 'paragraph']], ['table', ['table']], //['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled - //['insert', ['link', 'picture']], // modal popup has issue somehow :( - ['view', ['fullscreen', 'help']], + ['insert', ['link']], //, 'picture']], // modal popup has issue somehow :( + ['view', ['fullscreen', 'codeview', 'help']], ]; const cleanPastedHTML = function(input) { const badTags = [ @@ -91,6 +91,7 @@ Template.editor.onRendered(() => { }; const editor = '.editor'; const selectors = [ + `.js-new-description-form ${editor}`, `.js-new-comment-form ${editor}`, `.js-edit-comment ${editor}`, ].join(','); // only new comment and edit comment @@ -144,6 +145,7 @@ Template.editor.onRendered(() => { const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL; const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO; const insertImage = src => { + // process all image upload types to the description/comment window const img = document.createElement('img'); img.src = src; img.setAttribute('width', '100%'); @@ -209,7 +211,16 @@ Template.editor.onRendered(() => { } } }, - onPaste() { + onPaste(e) { + var clipboardData = e.clipboardData; + var pastedData = clipboardData.getData('Text'); + + //if pasted data is an image, exit + if (!pastedData.length) { + e.preventDefault(); + return; + } + // clear up unwanted tag info when user pasted in text const thisNote = this; const updatePastedText = function(object) { @@ -233,17 +244,17 @@ Template.editor.onRendered(() => { }, }, dialogsInBody: true, - disableDragAndDrop: true, + spellCheck: true, + disableGrammar: false, + disableDragAndDrop: false, toolbar, popover: { image: [ - [ - 'image', - ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone'], - ], + ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']], ['float', ['floatLeft', 'floatRight', 'floatNone']], ['remove', ['removeMedia']], ], + link: [['link', ['linkDialogShow', 'unlink']]], table: [ ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], ['delete', ['deleteRow', 'deleteCol', 'deleteTable']], @@ -262,7 +273,38 @@ Template.editor.onRendered(() => { } }); -import sanitizeXss from 'xss'; +import DOMPurify from 'dompurify'; + +// Additional safeAttrValue function to allow for other specific protocols +// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114 + +/* +function mySafeAttrValue(tag, name, value, cssFilter) { + // only when the tag is 'a' and attribute is 'href' + // then use your custom function + if (tag === 'a' && name === 'href') { + // only filter the value if starts with 'cbthunderlink:' or 'aodroplink' + if ( + /^thunderlink:/gi.test(value) || + /^cbthunderlink:/gi.test(value) || + /^aodroplink:/gi.test(value) || + /^onenote:/gi.test(value) || + /^file:/gi.test(value) || + /^abasurl:/gi.test(value) || + /^conisio:/gi.test(value) || + /^mailspring:/gi.test(value) + ) { + return value; + } else { + // use the default safeAttrValue function to process all non cbthunderlinks + return sanitizeXss.safeAttrValue(tag, name, value, cssFilter); + } + } else { + // use the default safeAttrValue function to process it + return sanitizeXss.safeAttrValue(tag, name, value, cssFilter); + } +} +*/ // XXX I believe we should compute a HTML rendered field on the server that // would handle markdown and user mentions. We can simply have two @@ -277,7 +319,10 @@ Blaze.Template.registerHelper( const view = this; let content = Blaze.toHTML(view.templateContentBlock); const currentBoard = Boards.findOne(Session.get('currentBoard')); - if (!currentBoard) return HTML.Raw(sanitizeXss(content)); + if (!currentBoard) + return HTML.Raw( + DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }), + ); const knowedUsers = currentBoard.members.map(member => { const u = Users.findOne(member.userId); if (u) { @@ -321,7 +366,9 @@ Blaze.Template.registerHelper( content = content.replace(fullMention, Blaze.toHTML(link)); } - return HTML.Raw(sanitizeXss(content)); + return HTML.Raw( + DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }), + ); }), ); @@ -330,7 +377,7 @@ Template.viewer.events({ // the corresponding text). Clicking a link shouldn't fire these actions, stop // we stop these event at the viewer component level. 'click a'(event, templateInstance) { - let prevent = true; + const prevent = true; const userId = event.currentTarget.dataset.userid; if (userId) { Popup.open('member').call({ userId }, event, templateInstance); diff --git a/client/components/main/fonts.styl b/client/components/main/fonts.styl index fc8c8f009..5d6fb558f 100644 --- a/client/components/main/fonts.styl +++ b/client/components/main/fonts.styl @@ -15,3 +15,27 @@ local('Roboto-Bold'), url('/fonts/roboto-bold.woff2') format('woff2'), url('/fonts/roboto-bold.woff') format('woff') + +@font-face + font-family: 'Poppins' + font-style: normal + font-weight: 400 + src: local('Poppins'), + local('Poppins-Regular'), + url('/fonts/poppins-regular.woff') format('woff') + +@font-face + font-family: 'Poppins' + font-style: normal + font-weight: 500 + src: local('Poppins Medium'), + local('Poppins-Medium'), + url('/fonts/poppins-medium.woff') format('woff') + +@font-face + font-family: 'Poppins' + font-style: normal + font-weight: 700 + src: local('Poppins Bold'), + local('Poppins-Bold'), + url('/fonts/poppins-bold.woff') format('woff') diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade new file mode 100644 index 000000000..31e9584e4 --- /dev/null +++ b/client/components/main/globalSearch.jade @@ -0,0 +1,86 @@ +template(name="globalSearchHeaderBar") + if currentUser + h1 + i.fa.fa-search + | {{_ 'globalSearch-title'}} + +template(name="globalSearchModalTitle") + if currentUser + h2 + i.fa.fa-keyboard-o + | {{_ 'globalSearch-title'}} + +template(name="resultsPaged") + if resultsHeading.get + h1 + = resultsHeading.get + a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}") + each card in results.get + +resultCard(card) + table.global-search-footer + tr + td.global-search-previous-page + if hasPreviousPage.get + button.js-previous-page + | {{_ 'previous-page' }} + td.global-search-next-page(align="right") + if hasNextPage.get + button.js-next-page + | {{_ 'next-page' }} + +template(name="globalSearch") + if currentUser + .wrapper + form.global-search-page.js-search-query-form + input.global-search-query-input( + style="{# if hasResults.get #}display: inline-block;{#/if#}" + id="global-search-input" + type="text" + name="searchQuery" + placeholder="{{_ 'search-example'}}" + value="{{ query.get }}" + autofocus dir="auto" + ) + a.js-new-search.fa.fa-eraser + if searching.get + +spinner + else if hasResults.get + .global-search-results-list-wrapper + if hasQueryErrors.get + ul + each msg in errorMessages + li.global-search-error-messages + = msg + +resultsPaged(this) + else if serverError.get + .global-search-page + .global-search-help + h1 {{_ 'server-error' }} + +viewer + | {{_ 'server-error-troubleshooting' }} + else + .global-search-page + .global-search-help + h2 {{_ 'boards' }} + .lists-wrapper + each title in myBoardNames.get + span.card-label.list-title.js-board-title + = title + h2 {{_ 'lists' }} + .lists-wrapper + each title in myLists.get + span.card-label.list-title.js-list-title + = title + h2 {{_ 'label-colors' }} + .palette-colors: each label in labelColors + span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}") + = label.name + if myLabelNames.get.length + h2 {{_ 'label-names' }} + .lists-wrapper + each name in myLabelNames.get + span.card-label.list-title.js-label-name + = name + .global-search-instructions + +viewer + = searchInstructions diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js new file mode 100644 index 000000000..f032c89ed --- /dev/null +++ b/client/components/main/globalSearch.js @@ -0,0 +1,267 @@ +import { CardSearchPagedComponent } from '../../lib/cardSearch'; +import Boards from '../../../models/boards'; +import { Query, QueryErrors } from '../../../config/query-classes'; + +// const subManager = new SubsManager(); + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-due-cards-view-change': Popup.open('globalSearchViewChange'), + }, + ]; + }, +}).register('globalSearchHeaderBar'); + +Template.globalSearch.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +class GlobalSearchComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + this.myLists = new ReactiveVar([]); + this.myLabelNames = new ReactiveVar([]); + this.myBoardNames = new ReactiveVar([]); + this.parsingErrors = new QueryErrors(); + this.queryParams = null; + + Meteor.call('myLists', (err, data) => { + if (!err) { + this.myLists.set(data); + } + }); + + Meteor.call('myLabelNames', (err, data) => { + if (!err) { + this.myLabelNames.set(data); + } + }); + + Meteor.call('myBoardNames', (err, data) => { + if (!err) { + this.myBoardNames.set(data); + } + }); + } + + onRendered() { + Meteor.subscribe('setting'); + + // eslint-disable-next-line no-console + //console.log('lang:', TAPi18n.getLanguage()); + + if (Session.get('globalQuery')) { + this.searchAllBoards(Session.get('globalQuery')); + } + } + + resetSearch() { + super.resetSearch(); + this.parsingErrors = new QueryErrors(); + } + + errorMessages() { + if (this.parsingErrors.hasErrors()) { + return this.parsingErrors.errorMessages(); + } + return this.queryErrorMessages(); + } + + parsingErrorMessages() { + this.parsingErrors.errorMessages(); + } + + searchAllBoards(queryText) { + queryText = queryText.trim(); + // eslint-disable-next-line no-console + //console.log('queryText:', queryText); + + this.query.set(queryText); + + this.resetSearch(); + + if (!queryText) { + return; + } + + this.searching.set(true); + + const query = new Query(); + query.buildParams(queryText); + + // eslint-disable-next-line no-console + // console.log('params:', query.getParams()); + + this.queryParams = query.getQueryParams().getParams(); + + if (query.hasErrors()) { + this.searching.set(false); + this.queryErrors = query.errors(); + this.hasResults.set(true); + this.hasQueryErrors.set(true); + return; + } + + this.runGlobalSearch(query.getQueryParams()); + } + + searchInstructions() { + const tags = { + operator_board: TAPi18n.__('operator-board'), + operator_list: TAPi18n.__('operator-list'), + operator_swimlane: TAPi18n.__('operator-swimlane'), + operator_comment: TAPi18n.__('operator-comment'), + operator_label: TAPi18n.__('operator-label'), + operator_label_abbrev: TAPi18n.__('operator-label-abbrev'), + operator_user: TAPi18n.__('operator-user'), + operator_user_abbrev: TAPi18n.__('operator-user-abbrev'), + operator_member: TAPi18n.__('operator-member'), + operator_member_abbrev: TAPi18n.__('operator-member-abbrev'), + operator_assignee: TAPi18n.__('operator-assignee'), + operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'), + operator_creator: TAPi18n.__('operator-creator'), + operator_due: TAPi18n.__('operator-due'), + operator_created: TAPi18n.__('operator-created'), + operator_modified: TAPi18n.__('operator-modified'), + operator_status: TAPi18n.__('operator-status'), + operator_has: TAPi18n.__('operator-has'), + operator_sort: TAPi18n.__('operator-sort'), + operator_limit: TAPi18n.__('operator-limit'), + predicate_overdue: TAPi18n.__('predicate-overdue'), + predicate_archived: TAPi18n.__('predicate-archived'), + predicate_all: TAPi18n.__('predicate-all'), + predicate_ended: TAPi18n.__('predicate-ended'), + predicate_week: TAPi18n.__('predicate-week'), + predicate_month: TAPi18n.__('predicate-month'), + predicate_quarter: TAPi18n.__('predicate-quarter'), + predicate_year: TAPi18n.__('predicate-year'), + predicate_attachment: TAPi18n.__('predicate-attachment'), + predicate_description: TAPi18n.__('predicate-description'), + predicate_checklist: TAPi18n.__('predicate-checklist'), + predicate_public: TAPi18n.__('predicate-public'), + predicate_private: TAPi18n.__('predicate-private'), + predicate_due: TAPi18n.__('predicate-due'), + predicate_created: TAPi18n.__('predicate-created'), + predicate_modified: TAPi18n.__('predicate-modified'), + predicate_start: TAPi18n.__('predicate-start'), + predicate_end: TAPi18n.__('predicate-end'), + predicate_assignee: TAPi18n.__('predicate-assignee'), + predicate_member: TAPi18n.__('predicate-member'), + }; + + let text = ''; + [ + ['# ', 'globalSearch-instructions-heading'], + ['\n', 'globalSearch-instructions-description'], + ['\n\n', 'globalSearch-instructions-operators'], + ['\n- ', 'globalSearch-instructions-operator-board'], + ['\n- ', 'globalSearch-instructions-operator-list'], + ['\n- ', 'globalSearch-instructions-operator-swimlane'], + ['\n- ', 'globalSearch-instructions-operator-comment'], + ['\n- ', 'globalSearch-instructions-operator-label'], + ['\n- ', 'globalSearch-instructions-operator-hash'], + ['\n- ', 'globalSearch-instructions-operator-user'], + ['\n- ', 'globalSearch-instructions-operator-at'], + ['\n- ', 'globalSearch-instructions-operator-member'], + ['\n- ', 'globalSearch-instructions-operator-assignee'], + ['\n- ', 'globalSearch-instructions-operator-creator'], + ['\n- ', 'globalSearch-instructions-operator-due'], + ['\n- ', 'globalSearch-instructions-operator-created'], + ['\n- ', 'globalSearch-instructions-operator-modified'], + ['\n- ', 'globalSearch-instructions-operator-status'], + ['\n - ', 'globalSearch-instructions-status-archived'], + ['\n - ', 'globalSearch-instructions-status-public'], + ['\n - ', 'globalSearch-instructions-status-private'], + ['\n - ', 'globalSearch-instructions-status-all'], + ['\n - ', 'globalSearch-instructions-status-ended'], + ['\n- ', 'globalSearch-instructions-operator-has'], + ['\n- ', 'globalSearch-instructions-operator-sort'], + ['\n- ', 'globalSearch-instructions-operator-limit'], + ['\n## ', 'heading-notes'], + ['\n- ', 'globalSearch-instructions-notes-1'], + ['\n- ', 'globalSearch-instructions-notes-2'], + ['\n- ', 'globalSearch-instructions-notes-3'], + ['\n- ', 'globalSearch-instructions-notes-3-2'], + ['\n- ', 'globalSearch-instructions-notes-4'], + ['\n- ', 'globalSearch-instructions-notes-5'], + ].forEach(([prefix, instruction]) => { + text += `${prefix}${TAPi18n.__(instruction, tags)}` + // Replace ** with `` so markdown shows correctly + .replace(/\*\\*/, '\>\`') + }); + return text; + } + + labelColors() { + return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map( + color => { + return { color, name: TAPi18n.__(`color-${color}`) }; + }, + ); + } + + events() { + return super.events().concat([ + { + 'submit .js-search-query-form'(evt) { + evt.preventDefault(); + this.searchAllBoards(evt.target.searchQuery.value); + }, + 'click .js-label-color'(evt) { + evt.preventDefault(); + const input = document.getElementById('global-search-input'); + this.query.set( + `${input.value} ${TAPi18n.__('operator-label')}:"${ + evt.currentTarget.textContent + }"`, + ); + document.getElementById('global-search-input').focus(); + }, + 'click .js-board-title'(evt) { + evt.preventDefault(); + const input = document.getElementById('global-search-input'); + this.query.set( + `${input.value} ${TAPi18n.__('operator-board')}:"${ + evt.currentTarget.textContent + }"`, + ); + document.getElementById('global-search-input').focus(); + }, + 'click .js-list-title'(evt) { + evt.preventDefault(); + const input = document.getElementById('global-search-input'); + this.query.set( + `${input.value} ${TAPi18n.__('operator-list')}:"${ + evt.currentTarget.textContent + }"`, + ); + document.getElementById('global-search-input').focus(); + }, + 'click .js-label-name'(evt) { + evt.preventDefault(); + const input = document.getElementById('global-search-input'); + this.query.set( + `${input.value} ${TAPi18n.__('operator-label')}:"${ + evt.currentTarget.textContent + }"`, + ); + document.getElementById('global-search-input').focus(); + }, + 'click .js-new-search'(evt) { + evt.preventDefault(); + const input = document.getElementById('global-search-input'); + input.value = ''; + this.query.set(''); + this.hasResults.set(false); + }, + }, + ]); + } +} + +GlobalSearchComponent.register('globalSearch'); diff --git a/client/components/main/globalSearch.styl b/client/components/main/globalSearch.styl new file mode 100644 index 000000000..66115d86c --- /dev/null +++ b/client/components/main/globalSearch.styl @@ -0,0 +1,121 @@ +.global-search-board-wrapper + border-radius: 8px + //padding: 0.5rem + min-width: 400px + border-width: 8px + border-color: grey + border-style: solid + margin-bottom: 2rem + margin-right: auto + margin-left: auto + +.global-search-board-title + font-size: 1.4rem + font-weight: bold + padding: 0.5rem + background-color: grey + color: white + +.global-search-swimlane-title + font-size: 1.1rem + font-weight: bold + padding: 0.5rem + padding-bottom: 0.4rem + margin-top: 0 + margin-bottom: 0.5rem + //border-top: black 1px solid + //border-bottom: black 1px solid + text-align: center + +.swimlane-default-color + background-color: lightgrey + +.global-search-list-title + font-weight: bold + font-size: 1.1rem + //padding-bottom: 0 + //margin-bottom: 0 + text-align: center + margin-bottom: 0.7rem + +.global-search-list-wrapper + margin: 1rem + border-radius: 5px + padding: 1.5rem + padding-top: 0.75rem + display: inline-block + min-width: 250px + max-width: 350px + +.global-search-card-wrapper + margin-top: 0 + margin-bottom: 10px + +.global-search-results-list-wrapper + max-width: 500px + margin-right: auto + margin-left: auto + +.global-search-field-name + font-weight: bold + +.global-search-context + display: inline-block + +.global-search-context-separator + font-weight: bold + +.global-search-context-list + margin-bottom: 0.7rem + +.global-search-error-messages + color: darkred + +.global-search-page + width: 40% + min-width: 400px + margin-right: auto + margin-left: auto + line-height: 150% + +.global-search-page h1 + margin-top: 2rem; + +.global-search-page h2 + margin-top: 1rem; + +.global-search-query-input + width: 90% !important + margin-right: auto + margin-left: auto + +.global-search-operator + font-family: Courier + +.global-search-value + font-family: Courier + font-style: italic + +code + color: black + background-color: lightgrey + padding: 0.1rem !important + font-size: 0.8rem !important + +.list-title + background-color: darkgray + +.global-search-footer + border: none + width: 100% + +.global-search-next-page + border: none + text-align: right; + +.global-search-previous-page + border: none + text-align: left; + +.global-search-instructions li + margin-bottom: 0.3rem diff --git a/client/components/main/header.jade b/client/components/main/header.jade index 9a5a6b9b8..05af9aded 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -7,33 +7,59 @@ template(name="header") if currentUser #header-quick-access(class=currentBoard.colorClass) if isMiniScreen - ul - li - a(href="{{pathFor 'home'}}") - span.fa.fa-home + span + a(href="{{pathFor 'home'}}") + span.fa.fa-home + ul.header-quick-access-list if currentList each currentBoard.lists li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}") a.js-select-list = title + else + each currentUser.starredBoards + li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") + a(href="{{pathFor 'board' id=_id slug=slug}}") + = title #header-new-board-icon else - ul - li - a(href="{{pathFor 'home'}}") - span.fa.fa-home - | {{_ 'all-boards'}} + //- + On sandstorm, the logo shouldn't be clickable, because we only have one + page/document on it, and we don't want to see the home page containing + the list of all boards. + unless currentSetting.hideLogo + if currentSetting.customTopLeftCornerLogoImageUrl + if currentSetting.customTopLeftCornerLogoLinkUrl + a(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}") + img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0") + unless currentSetting.customTopLeftCornerLogoLinkUrl + img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}") + unless currentSetting.customTopLeftCornerLogoImageUrl + img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}") + span.allBoards + a(href="{{pathFor 'home'}}") + span.fa.fa-home + | {{_ 'all-boards'}} + ul.header-quick-access-list + //li + // a(href="{{pathFor 'public'}}") + // span.fa.fa-globe + // | {{_ 'public'}} each currentUser.starredBoards - li.separator - li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") a(href="{{pathFor 'board' id=_id slug=slug}}") = title else - li.current {{_ 'quick-access-description'}} + li.current.empty {{_ 'quick-access-description'}} - a#header-new-board-icon.js-create-board - i.fa.fa-plus(title="Create a new board") + // Next line is used only for spacing at header, + // there is no visible clickable icon. + #header-new-board-icon + // Hide duplicate create board button, + // because it did not show board templates correctly. + //a#header-new-board-icon.js-create-board + // i.fa.fa-plus(title="Create a new board") +notifications @@ -47,17 +73,6 @@ template(name="header") #header-main-bar(class="{{#if wrappedHeader}}wrapper{{/if}}") +Template.dynamic(template=headerBar) - //unless hideLogo - - //- - On sandstorm, the logo shouldn't be clickable, because we only have one - page/document on it, and we don't want to see the home page containing - the list of all boards. - - // unless currentSetting.hideLogo - // a.wekan-logo(href="{{pathFor 'home'}}" title="{{_ 'header-logo-title'}}") - // img(src="{{pathFor '/logo-header.png'}}" alt="") - if appIsOffline +offlineWarning diff --git a/client/components/main/header.styl b/client/components/main/header.styl index 632d1535f..f396d69a7 100644 --- a/client/components/main/header.styl +++ b/client/components/main/header.styl @@ -100,6 +100,10 @@ font-size: 12px display: flex z-index: 21 + padding: 10px 0px + + .allBoards + padding: 0 0 0 10px #header-user-bar, #header-new-board-icon, @@ -112,22 +116,32 @@ a:hover, a.is-active color: white - ul + ul.header-quick-access-list transition: opacity 0.2s - margin: 4px 0 0 5px - overflow: hidden + overflow-x: auto + overflow-y: hidden + white-space: nowrap + padding: 10px + margin: -10px li - display: block - float: left + display: inline width: auto color: darken(white, 15%) - padding: 2px 5px 0 + padding: 12px 0px + margin: -10px 0px + + a + padding: 12px 10px + margin: -10px 0px &.current color: darken(white, 5%) - &:first-child .fa-home + &.current.empty + padding: 12px 10px 12px 10px + + &:first-child .fa-home,&:nth-child(3) .fa-globe margin-right: 5px a.js-create-board @@ -175,7 +189,7 @@ .board-header-btn height: 32px line-height: @height - font-size: 16px + font-size: 15px i.fa line-height: 32px @@ -186,37 +200,32 @@ #header-quick-access transition: background-color 0.4s width: 100% - padding: 10px 0px z-index: 30 ul width: calc(100% - 60px) - overflow: ellipsis - padding: 10px - margin: -10px + margin-right: 10px li height: 100% - padding: 12px 0px - margin: -10px 0px a height: 100% - padding: 12px 10px - margin: -10px 0px - .fa-home + span + .fa-home font-size: 26px margin-top: -2px + margin-right: 10px + margin-left: 10px #header-new-board-icon display: none #header-user-bar - position: absolute right: 0px padding: 10px - margin: -10px 0 -10px -10px + margin: -8px 0 -10px -10px .announcement .viewer display: inline-block diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade index 9543c5c50..3c87104f4 100644 --- a/client/components/main/layouts.jade +++ b/client/components/main/layouts.jade @@ -6,13 +6,40 @@ head where the application is deployed with a path prefix, but it seems to be difficult to do that cleanly with Blaze -- at least without adding extra packages. - link(rel="shortcut icon" href="/wekan-favicon.png") - link(rel="apple-touch-icon" href="/wekan-favicon.png") - link(rel="mask-icon" href="/wekan-logo-150.svg") - link(rel="manifest" href="/wekan-manifest.json") + link(rel="shortcut icon" type="image/x-icon" href="/favicon.ico") + link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png") + link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png") + link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png") + link(rel="manifest" href="/site.webmanifest") + link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5") + meta(name="apple-mobile-web-app-title" content="Wekan") + meta(name="application-name" content="Wekan") + meta(name="msapplication-TileColor" content="#00aba9") + meta(name="theme-color" content="#ffffff") template(name="userFormsLayout") section.auth-layout + if currentSetting.hideLogo + h1.at-form-landing-logo + br + br + unless currentSetting.hideLogo + h1.at-form-landing-logo + if currentSetting.customLoginLogoImageUrl + if currentSetting.customLoginLogoLinkUrl + a(href="{{currentSetting.customLoginLogoLinkUrl}}") + img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto") + br + unless currentSetting.customLoginLogoLinkUrl + img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto") + br + unless currentSetting.customLoginLogoImageUrl + img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto") + br + if currentSetting.textBelowCustomLoginLogo + +viewer + | {{currentSetting.textBelowCustomLoginLogo}} + br section.auth-dialog if isLoading +loader diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index a4768900d..cb06804e3 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -31,6 +31,11 @@ Template.userFormsLayout.onCreated(function() { return this.stop(); }, }); + Meteor.call('isPasswordLoginDisabled', (_, result) => { + if (result) { + $('.at-pwd-form').hide(); + } + }); }); Template.userFormsLayout.onRendered(() => { @@ -69,11 +74,55 @@ Template.userFormsLayout.helpers({ let name = lang.name; if (lang.name === 'br') { name = 'Brezhoneg'; + } else if (lang.name === 'ar-EG') { + // ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo) + name = 'مَصرى'; + } else if (lang.name === 'de-CH') { + name = 'Deutsch (Schweiz)'; + } else if (lang.name === 'fa-IR') { + // fa-IR = Persian (Iran) + name = 'فارسی/پارسی (ایران‎)'; + } else if (lang.name === 'fr-BE') { + name = 'Français (Belgique)'; + } else if (lang.name === 'fr-CA') { + name = 'Français (Canada)'; } else if (lang.name === 'ig') { name = 'Igbo'; + } else if (lang.name === 'lv') { + name = 'Latviešu'; + } else if (lang.name === 'latviešu valoda') { + name = 'Latviešu'; + } else if (lang.name === 'Español') { + name = 'español'; + } else if (lang.name === 'es_419') { + name = 'español de América Latina'; + } else if (lang.name === 'es-419') { + name = 'español de América Latina'; + } else if (lang.name === 'Español de América Latina') { + name = 'español de América Latina'; + } else if (lang.name === 'es-LA') { + name = 'español de América Latina'; + } else if (lang.name === 'Español de Argentina') { + name = 'español de Argentina'; + } else if (lang.name === 'Español de Chile') { + name = 'español de Chile'; + } else if (lang.name === 'Español de Colombia') { + name = 'español de Colombia'; + } else if (lang.name === 'Español de México') { + name = 'español de México'; + } else if (lang.name === 'es-PY') { + name = 'español de Paraguayo'; + } else if (lang.name === 'Español de Paraguayo') { + name = 'español de Paraguayo'; + } else if (lang.name === 'Español de Perú') { + name = 'español de Perú'; + } else if (lang.name === 'Español de Puerto Rico') { + name = 'español de Puerto Rico'; } else if (lang.name === 'oc') { name = 'Occitan'; - } else if (lang.name === 'zh-TW') { + } else if (lang.name === 'st') { + name = 'Sãotomense'; + } else if (lang.name === '繁体中文(台湾)') { name = '繁體中文(台灣)'; } return { tag, name }; @@ -140,6 +189,19 @@ async function authentication(event, templateInstance) { }); }); + case 'saml': + return new Promise(resolve => { + const provider = Meteor.settings.public.SAML_PROVIDER; + Meteor.loginWithSaml( + { + provider, + }, + function() { + resolve(FlowRouter.go('/')); + }, + ); + }); + case 'cas': return new Promise(resolve => { Meteor.loginWithCas(match, password, function() { diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl index 01ce2f160..f0faa6f38 100644 --- a/client/components/main/layouts.styl +++ b/client/components/main/layouts.styl @@ -32,7 +32,7 @@ a:hover,a:focus border-radius: unset html, body, input, select, textarea, button - font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif + font: 14px Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif line-height: 18px color: #4d4d4d @@ -276,7 +276,7 @@ kbd padding-bottom: 0 .wrapper - max-width: 1200px + width: calc(100% - 20px) margin: 0 auto .relative @@ -388,10 +388,12 @@ a ol list-style-type: decimal padding-left: 20px + padding-bottom: 10px ul list-style-type: initial padding-left: 20px + padding-bottom: 10px em font-style : italic @@ -447,7 +449,6 @@ a flex-direction: column align-items: center justify-content: center - height: 100% .auth-dialog margin: 0 !important diff --git a/client/components/main/myCards.jade b/client/components/main/myCards.jade new file mode 100644 index 000000000..8f2fcac5c --- /dev/null +++ b/client/components/main/myCards.jade @@ -0,0 +1,39 @@ +template(name="myCardsHeaderBar") + if currentUser + h1 + //a.back-btn(href="{{pathFor 'home'}}") + // i.fa.fa-chevron-left + i.fa.fa-list + | {{_ 'my-cards'}} + +template(name="myCardsModalTitle") + if currentUser + h2 + i.fa.fa-keyboard-o + | {{_ 'my-cards'}} + +template(name="myCards") + if currentUser + if searching.get + +spinner + else + .wrapper + each board in myCardsList + .my-cards-board-wrapper + .my-cards-board-title(class=board.colorClass, id="header") + a(href=board.originRelativeUrl) + +viewer + = board.title + each swimlane in board.mySwimlanes + .my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}") + +viewer + = swimlane.title + each list in swimlane.myLists + .my-cards-list-wrapper + .my-cards-list-title(class=list.colorClass) + +viewer + = list.title + each card in list.myCards + .my-cards-card-wrapper + a.minicard-wrapper(href=card.originRelativeUrl) + +minicard(card) diff --git a/client/components/main/myCards.js b/client/components/main/myCards.js new file mode 100644 index 000000000..07171dc07 --- /dev/null +++ b/client/components/main/myCards.js @@ -0,0 +1,138 @@ +import { CardSearchPagedComponent } from '../../lib/cardSearch'; + +BlazeComponent.extendComponent({ + myCardsSort() { + // eslint-disable-next-line no-console + // console.log('sort:', Utils.myCardsSort()); + return Utils.myCardsSort(); + }, + + events() { + return [ + { + 'click .js-toggle-my-cards-choose-sort': Popup.open( + 'myCardsSortChange', + ), + }, + ]; + }, +}).register('myCardsHeaderBar'); + +Template.myCards.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +class MyCardsComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + + this.runGlobalSearch(null); + Meteor.subscribe('setting'); + } + + // eslint-disable-next-line no-unused-vars + getSubscription(queryParams) { + return Meteor.subscribe( + 'myCards', + this.sessionId, + this.subscriptionCallbacks, + ); + } + + myCardsList() { + const boards = []; + let board = null; + let swimlane = null; + let list = null; + + const cursor = this.getResults(); + + if (cursor) { + cursor.forEach(card => { + // eslint-disable-next-line no-console + // console.log('card:', card.title); + if (board === null || card.boardId !== board._id) { + // eslint-disable-next-line no-console + // console.log('new board'); + board = card.getBoard(); + if (board.archived) { + board = null; + return; + } + // eslint-disable-next-line no-console + // console.log('board:', b, b._id, b.title); + boards.push(board); + board.mySwimlanes = []; + swimlane = null; + list = null; + } + + if (swimlane === null || card.swimlaneId !== swimlane._id) { + // eslint-disable-next-line no-console + // console.log('new swimlane'); + swimlane = card.getSwimlane(); + if (swimlane.archived) { + swimlane = null; + return; + } + board.mySwimlanes.push(swimlane); + swimlane.myLists = []; + list = null; + } + + if (list === null || card.listId !== list._id) { + // eslint-disable-next-line no-console + // console.log('new list'); + list = card.getList(); + if (list.archived) { + list = null; + return; + } + swimlane.myLists.push(list); + list.myCards = []; + } + + list.myCards.push(card); + }); + + // sort the data structure + boards.forEach(board => { + board.mySwimlanes.forEach(swimlane => { + swimlane.myLists.forEach(list => { + list.myCards.sort((a, b) => { + return a.sort - b.sort; + }); + }); + swimlane.myLists.sort((a, b) => { + return a.sort - b.sort; + }); + }); + board.mySwimlanes.sort((a, b) => { + return a.sort - b.sort; + }); + }); + + boards.sort((a, b) => { + let x = a.sort; + let y = b.sort; + + // show the template board last + if (a.type === 'template-container') { + x = 99999999; + } else if (b.type === 'template-container') { + y = 99999999; + } + return x - y; + }); + + // eslint-disable-next-line no-console + // console.log('boards:', boards); + return boards; + } + + return []; + } +} +MyCardsComponent.register('myCards'); diff --git a/client/components/main/myCards.styl b/client/components/main/myCards.styl new file mode 100644 index 000000000..afd108362 --- /dev/null +++ b/client/components/main/myCards.styl @@ -0,0 +1,57 @@ +.my-cards-board-wrapper + border-radius: 0 0 4px 4px; + //padding: 0.5rem + 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: grey + color: white + +.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 + //border-top: black 1px solid + //border-bottom: black 1px solid + text-align: center + +.swimlane-default-color + background-color: lightgrey + +.my-cards-list-title + font-weight: bold + font-size: 1.1rem + //padding-bottom: 0 + //margin-bottom: 0 + text-align: center + margin-bottom: 0.7rem + +.my-cards-list-wrapper + margin: 1rem + border-radius: 5px + //padding: 1.5rem + //padding-top: 0.75rem + 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 diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl index 023cba3d6..91f5fa039 100644 --- a/client/components/main/popup.styl +++ b/client/components/main/popup.styl @@ -107,11 +107,6 @@ $popupWidth = 300px padding: 8px 4px 8px 10px margin-right: 8px - &::-webkit-scrollbar-button - display: block - height: 4px - width: 4px - .at-form .at-error, .at-result padding: 8px 12px @@ -135,6 +130,10 @@ $popupWidth = 300px margin-bottom: 8px .pop-over-list + li + display: block + clear: both + li > a clear: both cursor: pointer @@ -316,6 +315,7 @@ $popupWidth = 300px input[type="file"] margin: 4px 0 12px width: 100% + box-sizing: border-box .pop-over-list li > a diff --git a/client/components/main/spinner.jade b/client/components/main/spinner.jade new file mode 100644 index 000000000..1b4d6dad0 --- /dev/null +++ b/client/components/main/spinner.jade @@ -0,0 +1,5 @@ +template(name="spinner") + +Template.dynamic(template=getSpinnerTemplate) + +template(name="spinnerRaw") + +Template.dynamic(template=getSpinnerTemplateRaw) diff --git a/client/components/main/spinner.js b/client/components/main/spinner.js new file mode 100644 index 000000000..2d7ab35f7 --- /dev/null +++ b/client/components/main/spinner.js @@ -0,0 +1,11 @@ +import { Spinner } from '/client/lib/spinner'; + +(class extends Spinner { +}.register('spinner')); + +(class extends Spinner { + getSpinnerTemplateRaw() { + let ret = super.getSpinnerTemplate() + 'Raw'; + return ret; + } +}.register('spinnerRaw')); diff --git a/client/components/main/spinner.tpl.jade b/client/components/main/spinner.tpl.jade deleted file mode 100644 index 9310a6e5b..000000000 --- a/client/components/main/spinner.tpl.jade +++ /dev/null @@ -1,6 +0,0 @@ -.sk-spinner.sk-spinner-wave(class=currentBoard.colorClass) - .sk-rect1 - .sk-rect2 - .sk-rect3 - .sk-rect4 - .sk-rect5 diff --git a/client/components/main/spinner_bounce.jade b/client/components/main/spinner_bounce.jade new file mode 100644 index 000000000..5eda2295a --- /dev/null +++ b/client/components/main/spinner_bounce.jade @@ -0,0 +1,11 @@ +template(name="spinnerBounce") + .sk-spinner.sk-spinner-bounce(class=currentBoard.colorClass) + +spinnerBounceRaw + +template(name="spinnerBounceRaw") + .sk-bounce1 + |   + .sk-bounce2 + |   + .sk-bounce3 + |   diff --git a/client/components/main/spinner_bounce.styl b/client/components/main/spinner_bounce.styl new file mode 100644 index 000000000..7d9ab0248 --- /dev/null +++ b/client/components/main/spinner_bounce.styl @@ -0,0 +1,44 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-bounce { + margin: 100px auto 0; + width: 70px; + text-align: center; + + div { + width: 18px; + height: 18px; + background-color: #333; + + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; + } + + .sk-bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } + + .sk-bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + } +} + +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} diff --git a/client/components/main/spinner_cube.jade b/client/components/main/spinner_cube.jade new file mode 100644 index 000000000..696d2f5ad --- /dev/null +++ b/client/components/main/spinner_cube.jade @@ -0,0 +1,8 @@ +template(name="spinnerCube") + .sk-spinner.sk-spinner-cube(class=currentBoard.colorClass) + +spinnerCubeRaw + +template(name="spinnerCubeRaw") + .sk-cube1 + .sk-cube2 + .sk-cube3 diff --git a/client/components/main/spinner_cube.styl b/client/components/main/spinner_cube.styl new file mode 100644 index 000000000..92e6e2a11 --- /dev/null +++ b/client/components/main/spinner_cube.styl @@ -0,0 +1,52 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-cube { + margin: 100px auto; + width: 40px; + height: 40px; + position: relative; +} + +.sk-cube1, .sk-cube2 { + background-color: #333; + width: 15px; + height: 15px; + position: absolute; + top: 0; + left: 0; + + -webkit-animation: sk-cubemove 1.8s infinite ease-in-out; + animation: sk-cubemove 1.8s infinite ease-in-out; +} + +.sk-cube2 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; +} + +@-webkit-keyframes sk-cubemove { + 25% { -webkit-transform: translateX(35px) rotate(-90deg) scale(0.5) } + 50% { -webkit-transform: translateX(35px) translateY(35px) rotate(-180deg) } + 75% { -webkit-transform: translateX(0px) translateY(35px) rotate(-270deg) scale(0.5) } + 100% { -webkit-transform: rotate(-360deg) } +} + +@keyframes sk-cubemove { + 25% { + transform: translateX(35px) rotate(-90deg) scale(0.5); + -webkit-transform: translateX(35px) rotate(-90deg) scale(0.5); + } 50% { + transform: translateX(35px) translateY(35px) rotate(-179deg); + -webkit-transform: translateX(35px) translateY(35px) rotate(-179deg); + } 50.1% { + transform: translateX(35px) translateY(35px) rotate(-180deg); + -webkit-transform: translateX(35px) translateY(35px) rotate(-180deg); + } 75% { + transform: translateX(0px) translateY(35px) rotate(-270deg) scale(0.5); + -webkit-transform: translateX(0px) translateY(35px) rotate(-270deg) scale(0.5); + } 100% { + transform: rotate(-360deg); + -webkit-transform: rotate(-360deg); + } +} diff --git a/client/components/main/spinner_cube_grid.jade b/client/components/main/spinner_cube_grid.jade new file mode 100644 index 000000000..55faf11af --- /dev/null +++ b/client/components/main/spinner_cube_grid.jade @@ -0,0 +1,14 @@ +template(name="spinnerCubeGrid") + .sk-spinner.sk-spinner-cube-grid(class=currentBoard.colorClass) + +spinnerCubeGridRaw + +template(name="spinnerCubeGridRaw") + .sk-cube-grid.sk-cube-grid1 + .sk-cube-grid.sk-cube-grid2 + .sk-cube-grid.sk-cube-grid3 + .sk-cube-grid.sk-cube-grid4 + .sk-cube-grid.sk-cube-grid5 + .sk-cube-grid.sk-cube-grid6 + .sk-cube-grid.sk-cube-grid7 + .sk-cube-grid.sk-cube-grid8 + .sk-cube-grid.sk-cube-grid9 diff --git a/client/components/main/spinner_cube_grid.styl b/client/components/main/spinner_cube_grid.styl new file mode 100644 index 000000000..042aa10fe --- /dev/null +++ b/client/components/main/spinner_cube_grid.styl @@ -0,0 +1,64 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-cube-grid { + width: 40px; + height: 40px; + margin: 100px auto; +} + +.sk-spinner-cube-grid .sk-cube-grid { + width: 33%; + height: 33%; + background-color: #333; + float: left; + -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; + animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; +} +.sk-spinner-cube-grid .sk-cube-grid1 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } +.sk-spinner-cube-grid .sk-cube-grid2 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; } +.sk-spinner-cube-grid .sk-cube-grid3 { + -webkit-animation-delay: 0.4s; + animation-delay: 0.4s; } +.sk-spinner-cube-grid .sk-cube-grid4 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; } +.sk-spinner-cube-grid .sk-cube-grid5 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } +.sk-spinner-cube-grid .sk-cube-grid6 { + -webkit-animation-delay: 0.3s; + animation-delay: 0.3s; } +.sk-spinner-cube-grid .sk-cube-grid7 { + -webkit-animation-delay: 0s; + animation-delay: 0s; } +.sk-spinner-cube-grid .sk-cube-grid8 { + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; } +.sk-spinner-cube-grid .sk-cube-grid9 { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; } + +@-webkit-keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} + +@keyframes sk-cubeGridScaleDelay { + 0%, 70%, 100% { + -webkit-transform: scale3D(1, 1, 1); + transform: scale3D(1, 1, 1); + } 35% { + -webkit-transform: scale3D(0, 0, 1); + transform: scale3D(0, 0, 1); + } +} diff --git a/client/components/main/spinner_dot.jade b/client/components/main/spinner_dot.jade new file mode 100644 index 000000000..187511116 --- /dev/null +++ b/client/components/main/spinner_dot.jade @@ -0,0 +1,7 @@ +template(name="spinnerDot") + .sk-spinner.sk-spinner-dot(class=currentBoard.colorClass) + +spinnerDotRaw + +template(name="spinnerDotRaw") + .sk-dot1 + .sk-dot2 diff --git a/client/components/main/spinner_dot.styl b/client/components/main/spinner_dot.styl new file mode 100644 index 000000000..5f2e36da4 --- /dev/null +++ b/client/components/main/spinner_dot.styl @@ -0,0 +1,51 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-dot { + margin: 100px auto; + width: 40px; + height: 40px; + position: relative; + text-align: center; + + -webkit-animation: sk-rotate 2.0s infinite linear; + animation: sk-rotate 2.0s infinite linear; +} + +.sk-dot1, .sk-dot2 { + width: 40%; + height: 40%; + display: inline-block; + position: absolute; + top: 0; + background-color: #333; + border-radius: 100%; + + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} + +.sk-dot2 { + top: auto; + bottom: 0; + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +@-webkit-keyframes sk-rotate { 100% { -webkit-transform: rotate(360deg) }} +@keyframes sk-rotate { 100% { transform: rotate(360deg); -webkit-transform: rotate(360deg) }} + +@-webkit-keyframes sk-bounce { + 0%, 100% { -webkit-transform: scale(0.0) } + 50% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} diff --git a/client/components/main/spinner_double_bounce.jade b/client/components/main/spinner_double_bounce.jade new file mode 100644 index 000000000..206a468d2 --- /dev/null +++ b/client/components/main/spinner_double_bounce.jade @@ -0,0 +1,7 @@ +template(name="spinnerDoubleBounce") + .sk-spinner.sk-spinner-double-bounce(class=currentBoard.colorClass) + +spinnerDoubleBounceRaw + +template(name="spinnerDoubleBounceRaw") + .sk-double-bounce1 + .sk-double-bounce2 diff --git a/client/components/main/spinner_double_bounce.styl b/client/components/main/spinner_double_bounce.styl new file mode 100644 index 000000000..2ef000361 --- /dev/null +++ b/client/components/main/spinner_double_bounce.styl @@ -0,0 +1,44 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-double-bounce { + width: 40px; + height: 40px; + + position: relative; + margin: 100px auto; +} + +.sk-double-bounce1, .sk-double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} + +.sk-double-bounce2 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +@-webkit-keyframes sk-bounce { + 0%, 100% { -webkit-transform: scale(0.0) } + 50% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} diff --git a/client/components/main/spinner_rotateplane.jade b/client/components/main/spinner_rotateplane.jade new file mode 100644 index 000000000..35afeda81 --- /dev/null +++ b/client/components/main/spinner_rotateplane.jade @@ -0,0 +1,6 @@ +template(name="spinnerRotateplane") + .sk-spinner.sk-spinner-rotateplane(class=currentBoard.colorClass) + +spinnerRotateplaneRaw + +template(name="spinnerRotateplaneRaw") + .sk-rotateplane1 diff --git a/client/components/main/spinner_rotateplane.styl b/client/components/main/spinner_rotateplane.styl new file mode 100644 index 000000000..1f43d37e3 --- /dev/null +++ b/client/components/main/spinner_rotateplane.styl @@ -0,0 +1,38 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-rotateplane { + width: 40px; + height: 40px; + text-align: center; + + margin: 100px auto; + -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out; + animation: sk-rotateplane 1.2s infinite ease-in-out; + + div { + background-color: #333; + height: 100%; + width: 100%; + display: inline-block; + } +} + +@-webkit-keyframes sk-rotateplane { + 0% { -webkit-transform: perspective(120px) } + 50% { -webkit-transform: perspective(120px) rotateY(180deg) } + 100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) } +} + +@keyframes sk-rotateplane { + 0% { + transform: perspective(120px) rotateX(0deg) rotateY(0deg); + -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg) + } 50% { + transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); + -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) + } 100% { + transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); + -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); + } +} diff --git a/client/components/main/spinner_scaleout.jade b/client/components/main/spinner_scaleout.jade new file mode 100644 index 000000000..2cb21d156 --- /dev/null +++ b/client/components/main/spinner_scaleout.jade @@ -0,0 +1,6 @@ +template(name="spinnerScaleout") + .sk-spinner.sk-spinner-scaleout(class=currentBoard.colorClass) + +spinnerScaleoutRaw + +template(name="spinnerScaleoutRaw") + .sk-scaleout1 diff --git a/client/components/main/spinner_scaleout.styl b/client/components/main/spinner_scaleout.styl new file mode 100644 index 000000000..deb73b68e --- /dev/null +++ b/client/components/main/spinner_scaleout.styl @@ -0,0 +1,40 @@ +@import 'nib' + +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-scaleout { + width: 40px; + height: 40px; + text-align: center; + + margin: 100px auto; + + border-radius: 100%; + -webkit-animation: sk-scaleout 1.0s infinite ease-in-out; + animation: sk-scaleout 1.0s infinite ease-in-out; + + div { + background-color: #333; + height: 100%; + width: 100%; + display: inline-block; + } +} + +@-webkit-keyframes sk-scaleout { + 0% { -webkit-transform: scale(0) } + 100% { + -webkit-transform: scale(1.0); + opacity: 0; + } +} + +@keyframes sk-scaleout { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + } 100% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + opacity: 0; + } +} diff --git a/client/components/main/spinner_wave.jade b/client/components/main/spinner_wave.jade new file mode 100644 index 000000000..fe5a26b0d --- /dev/null +++ b/client/components/main/spinner_wave.jade @@ -0,0 +1,15 @@ +template(name="spinnerWave") + .sk-spinner.sk-spinner-wave(class=currentBoard.colorClass) + +spinnerWaveRaw + +template(name="spinnerWaveRaw") + .sk-rect1 + |   + .sk-rect2 + |   + .sk-rect3 + |   + .sk-rect4 + |   + .sk-rect5 + |   diff --git a/client/components/main/spinner.styl b/client/components/main/spinner_wave.styl similarity index 61% rename from client/components/main/spinner.styl rename to client/components/main/spinner_wave.styl index 65c5fe62e..a82d39496 100644 --- a/client/components/main/spinner.styl +++ b/client/components/main/spinner_wave.styl @@ -1,21 +1,7 @@ @import 'nib' -/* - * From https://github.com/tobiasahlin/SpinKit - * - * Usage: - * - *

- *
- *
- *
- *
- *
- *
- * - */ - -.sk-spinner { +// From https://github.com/tobiasahlin/SpinKit +.sk-spinner-wave { width: 50px; height: 50px; margin: auto; diff --git a/client/components/mixins/perfectScrollbar.js b/client/components/mixins/perfectScrollbar.js deleted file mode 100644 index 12f8a8922..000000000 --- a/client/components/mixins/perfectScrollbar.js +++ /dev/null @@ -1,16 +0,0 @@ -const { isTouchDevice } = Utils; - -Mixins.PerfectScrollbar = BlazeComponent.extendComponent({ - onRendered() { - if (!isTouchDevice()) { - const component = this.mixinParent(); - const domElement = component.find('.js-perfect-scrollbar'); - Ps.initialize(domElement); - - // XXX We should create an event map to be consistent with other components - // but since BlazeComponent doesn't merge Mixins events transparently I - // prefered to use a jQuery event (which is what an event map ends up doing) - component.$(domElement).on('mouseenter', () => Ps.update(domElement)); - } - }, -}); diff --git a/client/components/mixins/perfectScrollbar.styl b/client/components/mixins/perfectScrollbar.styl deleted file mode 100644 index c82676685..000000000 --- a/client/components/mixins/perfectScrollbar.styl +++ /dev/null @@ -1,2 +0,0 @@ -.ps-container - position: relative diff --git a/client/components/notifications/notification.styl b/client/components/notifications/notification.styl index 0cf0cfd5a..b573fa6c7 100644 --- a/client/components/notifications/notification.styl +++ b/client/components/notifications/notification.styl @@ -5,7 +5,7 @@ .notification display: flex float: none - padding: 12px 8px 8px + padding: 15px 8px 8px color: black border-bottom: 1px solid #dbdbdb @@ -14,6 +14,7 @@ .read-status width: 30px + padding: 0px 10px 0px 0px input width: 24px @@ -28,13 +29,16 @@ color: #bbb .details - width: calc(100% - 30px) - .activity - display: flex + a.member + margin: 0px 0px 0px 0px + padding: 0px + + svg + padding: 3px .activity-desc - width: 100%; + margin: 0px 0px 0px 5px .activity-comment display: block @@ -55,3 +59,14 @@ .remove a:hover color #eb4646 !important + +@media screen and (max-width: 800px) + #notifications-drawer + .notification + height: auto + + .details + .activity + a.member + height: 36px + width: 36px diff --git a/client/components/notifications/notificationIcon.jade b/client/components/notifications/notificationIcon.jade index 043776065..ff35739c4 100644 --- a/client/components/notifications/notificationIcon.jade +++ b/client/components/notifications/notificationIcon.jade @@ -26,6 +26,10 @@ template(name='notificationIcon') i.fa.fa-code.activity-type(title="custom field") else if($in activityType 'addedLabel' 'removedLabel') i.fa.fa-tag.activity-type(title="label") + else if($in activityType 'a-startAt' 'a-receivedAt') + i.fa.fa-clock-o.activity-type(title="date") + else if($in activityType 'a-dueAt' 'a-endAt') + i.fa.fa-clock-o.activity-type(title="date") else if($in activityType 'createList' 'removeList' 'archivedList') +listNotificationIcon diff --git a/client/components/notifications/notifications.jade b/client/components/notifications/notifications.jade index bf8acbbf9..c58db37ca 100644 --- a/client/components/notifications/notifications.jade +++ b/client/components/notifications/notifications.jade @@ -1,5 +1,5 @@ template(name='notifications') #notifications.board-header-btns.right - a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}") + a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}" title="{{_ 'notifications'}}") if $.Session.get 'showNotificationsDrawer' +notificationsDrawer(unreadNotifications=unreadNotifications) diff --git a/client/components/notifications/notificationsDrawer.jade b/client/components/notifications/notificationsDrawer.jade index 01117009d..fee6aef67 100644 --- a/client/components/notifications/notificationsDrawer.jade +++ b/client/components/notifications/notificationsDrawer.jade @@ -14,3 +14,7 @@ template(name='notificationsDrawer') +notification(activityData=activity index=dbIndex read=read) if($gt unreadNotifications 0) a.all-read {{_ 'mark-all-as-read'}} + if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0)) + a.remove-read + i.fa.fa-trash + | {{_ 'remove-all-read'}} diff --git a/client/components/notifications/notificationsDrawer.js b/client/components/notifications/notificationsDrawer.js index 98d4750da..76abeea7d 100644 --- a/client/components/notifications/notificationsDrawer.js +++ b/client/components/notifications/notificationsDrawer.js @@ -16,6 +16,13 @@ Template.notificationsDrawer.helpers({ transformedProfile() { return Users.findOne(Meteor.userId()); }, + readNotifications() { + const readNotifications = _.filter( + Meteor.user().profile.notifications, + v => !!v.read, + ); + return readNotifications.length; + }, }); Template.notificationsDrawer.events({ @@ -35,4 +42,12 @@ Template.notificationsDrawer.events({ 'click .toggle-read'() { Session.set('showReadNotifications', !Session.get('showReadNotifications')); }, + 'click .remove-read'() { + const user = Meteor.user(); + for (const notification of user.profile.notifications) { + if (notification.read) { + user.removeNotification(notification.activity); + } + } + }, }); diff --git a/client/components/notifications/notificationsDrawer.styl b/client/components/notifications/notificationsDrawer.styl index a26b5e4a8..6414da683 100644 --- a/client/components/notifications/notificationsDrawer.styl +++ b/client/components/notifications/notificationsDrawer.styl @@ -2,7 +2,7 @@ belize = #2980b9 section#notifications-drawer position: fixed - top: 28px + top: 48px right: 0 width: 400px background-color: #fafafa @@ -10,15 +10,14 @@ section#notifications-drawer border-radius: 2px max-height: calc(100vh - 28px - 36px) color: black - padding-top 36px; - overflow: scroll + padding-top 36px a:hover color: belize !important .header position: fixed - top 28px + top 48px right 0 width calc(400px - 32px) padding: 8px 16px @@ -45,13 +44,26 @@ section#notifications-drawer line-height: 24px opacity 1 - .all-read + .all-read, + .remove-read color belize background-color: #fafafa margin 8px 16px 12px display inline-block + .remove-read + float right + + &:hover + color #eb4646 !important + + i.fa + color inherit + + ul.notifications display: block - padding: 0px 16px + padding: 0px 16px 0px 16px margin: 0 + height: calc(100vh - 122px) + overflow-y: scroll diff --git a/client/components/rules/.DS_Store b/client/components/rules/.DS_Store deleted file mode 100644 index 5008ddfcf..000000000 Binary files a/client/components/rules/.DS_Store and /dev/null differ diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js index c2f2375a5..5675873f3 100644 --- a/client/components/rules/actions/boardActions.js +++ b/client/components/rules/actions/boardActions.js @@ -11,7 +11,7 @@ BlazeComponent.extendComponent({ }, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); return boards; @@ -68,8 +68,8 @@ BlazeComponent.extendComponent({ const ruleName = this.data().ruleName.get(); const trigger = this.data().triggerVar.get(); const actionSelected = this.find('#move-spec-action').value; - const swimlaneName = this.find('#swimlaneName').value; - const listName = this.find('#listName').value; + const swimlaneName = this.find('#swimlaneName').value || '*'; + const listName = this.find('#listName').value || '*'; const boardId = Session.get('currentBoard'); const destBoardId = this.find('#board-id').value; const desc = Utils.getTriggerActionDesc(event, this); diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade index c10c4b2b7..aa31ca6da 100644 --- a/client/components/rules/actions/cardActions.jade +++ b/client/components/rules/actions/cardActions.jade @@ -3,32 +3,32 @@ template(name="cardActions") div.trigger-content div.trigger-dropdown select(id="setdate-action") - option(value="setDate") {{{_'r-set'}}} - option(value="updateDate") {{{_'r-update'}}} + option(value="setDate") {{_'r-set'}} + option(value="updateDate") {{_'r-update'}} div.trigger-text - | {{{_'r-datefield'}}} + | {{_'r-datefield'}} div.trigger-dropdown select(id="setdate-datefield") - option(value="startAt") {{{_'r-df-start-at'}}} - option(value="dueAt") {{{_'r-df-due-at'}}} - option(value="endAt") {{{_'r-df-end-at'}}} - option(value="receivedAt") {{{_'r-df-received-at'}}} + option(value="startAt") {{_'r-df-start-at'}} + option(value="dueAt") {{_'r-df-due-at'}} + option(value="endAt") {{_'r-df-end-at'}} + option(value="receivedAt") {{_'r-df-received-at'}} div.trigger-text - | {{{_'r-to-current-datetime'}}} + | {{_'r-to-current-datetime'}} div.trigger-button.js-set-date-action.js-goto-rules i.fa.fa-plus div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-remove-value-from'}}} - | {{{_'r-datefield'}}} + | {{_'r-remove-value-from'}} + | {{_'r-datefield'}} div.trigger-dropdown select(id="setdate-removedatefieldvalue") - option(value="startAt") {{{_'r-df-start-at'}}} - option(value="dueAt") {{{_'r-df-due-at'}}} - option(value="endAt") {{{_'r-df-end-at'}}} - option(value="receivedAt") {{{_'r-df-received-at'}}} + option(value="startAt") {{_'r-df-start-at'}} + option(value="dueAt") {{_'r-df-due-at'}} + option(value="endAt") {{_'r-df-end-at'}} + option(value="receivedAt") {{_'r-df-received-at'}} div.trigger-button.js-remove-datevalue-action.js-goto-rules i.fa.fa-plus @@ -36,10 +36,10 @@ template(name="cardActions") div.trigger-content div.trigger-dropdown select(id="label-action") - option(value="add") {{{_'r-add'}}} - option(value="remove") {{{_'r-remove'}}} + option(value="add") {{_'r-add'}} + option(value="remove") {{_'r-remove'}} div.trigger-text - | {{{_'r-label'}}} + | {{_'r-label'}} div.trigger-dropdown select(id="label-id") each labels @@ -52,30 +52,30 @@ template(name="cardActions") div.trigger-content div.trigger-dropdown select(id="member-action") - option(value="add") {{{_'r-add'}}} - option(value="remove") {{{_'r-remove'}}} + option(value="add") {{_'r-add'}} + option(value="remove") {{_'r-remove'}} div.trigger-text - | {{{_'r-member'}}} + | {{_'r-member'}} div.trigger-dropdown - input(id="member-name",type=text,placeholder="{{{_'r-name'}}}") + input(id="member-name",type=text,placeholder="{{_'r-name'}}") div.trigger-button.js-add-member-action.js-goto-rules i.fa.fa-plus div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-remove-all'}}} + | {{_'r-remove-all'}} div.trigger-button.js-add-removeall-action.js-goto-rules i.fa.fa-plus div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-set-color'}}} + | {{_'r-set-color'}} button.trigger-button.trigger-button-color.js-show-color-palette( id="color-action" class="card-details-{{cardColorButton}}") - | {{{_ cardColorButtonText }}} + | {{_ cardColorButtonText }} div.trigger-button.js-set-color-action.js-goto-rules i.fa.fa-plus diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js index 7dc6c2b55..2290249ca 100644 --- a/client/components/rules/actions/cardActions.js +++ b/client/components/rules/actions/cardActions.js @@ -164,6 +164,7 @@ BlazeComponent.extendComponent({ const boardId = Session.get('currentBoard'); const actionId = Actions.insert({ actionType: 'removeMember', + // deepcode ignore NoHardcodedCredentials: it's no credential username: '*', boardId, desc, diff --git a/client/components/rules/actions/checklistActions.jade b/client/components/rules/actions/checklistActions.jade index 94c63557d..0c2e04f13 100644 --- a/client/components/rules/actions/checklistActions.jade +++ b/client/components/rules/actions/checklistActions.jade @@ -3,12 +3,12 @@ template(name="checklistActions") div.trigger-content div.trigger-dropdown select(id="check-action") - option(value="add") {{{_'r-add'}}} - option(value="remove") {{{_'r-remove'}}} + option(value="add") {{_'r-add'}} + option(value="remove") {{_'r-remove'}} div.trigger-text - | {{{_'r-checklist'}}} + | {{_'r-checklist'}} div.trigger-dropdown - input(id="checklist-name",type=text,placeholder="{{{_'r-name'}}}") + input(id="checklist-name",type=text,placeholder="{{_'r-name'}}") div.trigger-button.js-add-checklist-action.js-goto-rules i.fa.fa-plus @@ -16,12 +16,12 @@ template(name="checklistActions") div.trigger-content div.trigger-dropdown select(id="checkall-action") - option(value="check") {{{_'r-check-all'}}} - option(value="uncheck") {{{_'r-uncheck-all'}}} + option(value="check") {{_'r-check-all'}} + option(value="uncheck") {{_'r-uncheck-all'}} div.trigger-text - | {{{_'r-items-check'}}} + | {{_'r-items-check'}} div.trigger-dropdown - input(id="checklist-name2",type=text,placeholder="{{{_'r-name'}}}") + input(id="checklist-name2",type=text,placeholder="{{_'r-name'}}") div.trigger-button.js-add-checkall-action.js-goto-rules i.fa.fa-plus @@ -30,36 +30,36 @@ template(name="checklistActions") div.trigger-content div.trigger-dropdown select(id="check-item-action") - option(value="check") {{{_'r-check'}}} - option(value="uncheck") {{{_'r-uncheck'}}} + option(value="check") {{_'r-check'}} + option(value="uncheck") {{_'r-uncheck'}} div.trigger-text - | {{{_'r-item'}}} + | {{_'r-item'}} div.trigger-dropdown - input(id="checkitem-name",type=text,placeholder="{{{_'r-name'}}}") + input(id="checkitem-name",type=text,placeholder="{{_'r-name'}}") div.trigger-text - | {{{_'r-of-checklist'}}} + | {{_'r-of-checklist'}} div.trigger-dropdown - input(id="checklist-name3",type=text,placeholder="{{{_'r-name'}}}") + input(id="checklist-name3",type=text,placeholder="{{_'r-name'}}") div.trigger-button.js-add-check-item-action.js-goto-rules i.fa.fa-plus div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-add-checklist'}}} + | {{_'r-add-checklist'}} div.trigger-dropdown - input(id="checklist-name-3",type=text,placeholder="{{{_'r-name'}}}") + input(id="checklist-name-3",type=text,placeholder="{{_'r-name'}}") div.trigger-text - | {{{_'r-with-items'}}} + | {{_'r-with-items'}} div.trigger-dropdown - input(id="checklist-items",type=text,placeholder="{{{_'r-items-list'}}}") + input(id="checklist-items",type=text,placeholder="{{_'r-items-list'}}") div.trigger-button.js-add-checklist-items-action.js-goto-rules i.fa.fa-plus div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-checklist-note'}}} + | {{_'r-checklist-note'}} diff --git a/client/components/rules/ruleDetails.jade b/client/components/rules/ruleDetails.jade index 7183cf967..57d52006d 100644 --- a/client/components/rules/ruleDetails.jade +++ b/client/components/rules/ruleDetails.jade @@ -2,14 +2,18 @@ template(name="ruleDetails") .rules h2 i.fa.fa-magic - | {{{_ 'r-rule-details' }}} + | {{_ 'r-rule-details' }} .triggers-content .triggers-body .triggers-main-body + h4 + | {{_ 'r-trigger'}} div.trigger-item div.trigger-content div.trigger-text - = trigger + = trigger + h4 + | {{_ 'r-action'}} div.trigger-item div.trigger-content div.trigger-text @@ -17,4 +21,4 @@ template(name="ruleDetails") div.rules-back button.js-goback i.fa.fa-chevron-left - | {{{_ 'back'}}} + | {{_ 'back'}} diff --git a/client/components/rules/rules.styl b/client/components/rules/rules.styl index 05302f7ff..1f973aae7 100644 --- a/client/components/rules/rules.styl +++ b/client/components/rules/rules.styl @@ -2,10 +2,12 @@ overflow:hidden overflow-y:scroll max-height: 400px + padding-right: 5px .rules-lists-item display: block position: relative overflow: auto + border-bottom: 1px solid #bfbfbf p display: inline-block float: left diff --git a/client/components/rules/rulesActions.jade b/client/components/rules/rulesActions.jade index 3ac04e1cb..26deae185 100644 --- a/client/components/rules/rulesActions.jade +++ b/client/components/rules/rulesActions.jade @@ -1,7 +1,7 @@ template(name="rulesActions") h2 i.fa.fa-magic - | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-action'}}} + | {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-action'}} .triggers-content .triggers-body .triggers-side-menu @@ -26,4 +26,4 @@ template(name="rulesActions") div.rules-back button.js-goback i.fa.fa-chevron-left - | {{{_ 'back'}}} + | {{_ 'back'}} diff --git a/client/components/rules/rulesList.jade b/client/components/rules/rulesList.jade index b8d49a667..3c59a19f3 100644 --- a/client/components/rules/rulesList.jade +++ b/client/components/rules/rulesList.jade @@ -2,7 +2,7 @@ template(name="rulesList") .rules h2 i.fa.fa-magic - | {{{_ 'r-board-rules' }}} + | {{_ 'r-board-rules' }} ul.rules-list each rules @@ -12,26 +12,26 @@ template(name="rulesList") div.rules-btns-group button.js-goto-details i.fa.fa-eye - | {{{_ 'r-view-rule'}}} + | {{_ 'r-view-rule'}} if currentUser.isAdmin button.js-delete-rule i.fa.fa-trash-o - | {{{_ 'r-delete-rule'}}} + | {{_ 'r-delete-rule'}} else if currentUser.isBoardAdmin button.js-delete-rule i.fa.fa-trash-o - | {{{_ 'r-delete-rule'}}} + | {{_ 'r-delete-rule'}} else - li.no-items-message {{{_ 'r-no-rules' }}} + li.no-items-message {{_ 'r-no-rules' }} if currentUser.isAdmin div.rules-add button.js-goto-trigger i.fa.fa-plus - | {{{_ 'r-add-rule'}}} - input(type=text,placeholder="{{{_ 'r-new-rule-name' }}}",id="ruleTitle") + | {{_ 'r-add-rule'}} + input(type=text,placeholder="{{_ 'r-new-rule-name' }}",id="ruleTitle") else if currentUser.isBoardAdmin div.rules-add button.js-goto-trigger i.fa.fa-plus - | {{{_ 'r-add-rule'}}} - input(type=text,placeholder="{{{_ 'r-new-rule-name' }}}",id="ruleTitle") + | {{_ 'r-add-rule'}} + input(type=text,placeholder="{{_ 'r-new-rule-name' }}",id="ruleTitle") diff --git a/client/components/rules/rulesTriggers.jade b/client/components/rules/rulesTriggers.jade index 79d9d98e1..03cf0e8fe 100644 --- a/client/components/rules/rulesTriggers.jade +++ b/client/components/rules/rulesTriggers.jade @@ -1,7 +1,7 @@ template(name="rulesTriggers") h2 i.fa.fa-magic - | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-trigger'}}} + | {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-trigger'}} .triggers-content .triggers-body .triggers-side-menu @@ -22,4 +22,4 @@ template(name="rulesTriggers") div.rules-back button.js-goback i.fa.fa-chevron-left - | {{{_ 'back'}}} + | {{_ 'back'}} diff --git a/client/components/rules/triggers/boardTriggers.jade b/client/components/rules/triggers/boardTriggers.jade index ff1406f65..cf13f8594 100644 --- a/client/components/rules/triggers/boardTriggers.jade +++ b/client/components/rules/triggers/boardTriggers.jade @@ -100,7 +100,7 @@ template(name="boardTriggers") div.trigger-item div.trigger-content div.trigger-text - | {{{_'r-board-note'}}} + | {{_'r-board-note'}} template(name="boardCardTitlePopup") form @@ -109,8 +109,3 @@ template(name="boardCardTitlePopup") input.js-card-filter-name(type="text" value=title autofocus) input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}") - - - - - diff --git a/client/components/settings/adminReports.jade b/client/components/settings/adminReports.jade new file mode 100644 index 000000000..ae32a1aa6 --- /dev/null +++ b/client/components/settings/adminReports.jade @@ -0,0 +1,111 @@ +template(name="adminReports") + .setting-content + unless currentUser.isAdmin + | {{_ 'error-notAuthorized'}} + else + .content-body + .side-menu + ul + li + a.js-report-broken(data-id="report-broken") + i.fa.fa-chain-broken + | {{_ 'broken-cards'}} + + li + a.js-report-files(data-id="report-orphaned-files") + i.fa.fa-paperclip + | {{_ 'orphanedFilesReportTitle'}} + + li + a.js-report-files(data-id="report-files") + i.fa.fa-paperclip + | {{_ 'filesReportTitle'}} + + li + a.js-report-rules(data-id="report-rules") + i.fa.fa-magic + | {{_ 'rulesReportTitle'}} + + .main-body + if loading.get + +spinner + else if showBrokenCardsReport.get + +brokenCardsReport + else if showFilesReport.get + +filesReport + else if showOrphanedFilesReport.get + +orphanedFilesReport + else if showRulesReport.get + +rulesReport + + +template(name="brokenCardsReport") + .global-search-results-list-wrapper + h1 {{_ 'broken-cards'}} + if resultsCount + +resultsPaged(this) + else + div {{_ 'no-results' }} + +template(name="rulesReport") + h1 {{_ 'rulesReportTitle'}} + if resultsCount + table.table + tr + th Rule Title + th Board Title + th actionType + th activityType + + each rule in rows + tr + td {{ rule.title }} + td {{ rule.boardTitle }} + td {{ rule.action.actionType }} + td {{ rule.trigger.activityType }} + else + div {{_ 'no-results' }} + +template(name="filesReport") + h1 {{_ 'filesReportTitle'}} + if resultsCount + table.table + tr + th Filename + th.right Size (kB) + th MIME Type + th.center Usage + th MD5 Sum + th ID + + each att in attachmentFiles + tr + td {{ att.filename }} + td.right {{fileSize att.length }} + td {{ att.contentType }} + td.center {{usageCount att._id.toHexString }} + td {{ att.md5 }} + td {{ att._id.toHexString }} + else + div {{_ 'no-results' }} + +template(name="orphanedFilesReport") + h1 {{_ 'orphanedFilesReportTitle'}} + if resultsCount + table.table + tr + th Filename + th.right Size (kB) + th MIME Type + th MD5 Sum + th ID + + each att in attachmentFiles + tr + td {{ att.filename }} + td.right {{fileSize att.length }} + td {{ att.contentType }} + td {{ att.md5 }} + td {{ att._id.toHexString }} + else + div {{_ 'no-results' }} diff --git a/client/components/settings/adminReports.js b/client/components/settings/adminReports.js new file mode 100644 index 000000000..e8ba75fc0 --- /dev/null +++ b/client/components/settings/adminReports.js @@ -0,0 +1,156 @@ +import { AttachmentStorage } from '/models/attachments'; +import { CardSearchPagedComponent } from '/client/lib/cardSearch'; +import SessionData from '/models/usersessiondata'; + +BlazeComponent.extendComponent({ + subscription: null, + showFilesReport: new ReactiveVar(false), + showBrokenCardsReport: new ReactiveVar(false), + showOrphanedFilesReport: new ReactiveVar(false), + showRulesReport: new ReactiveVar(false), + + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + }, + + events() { + return [ + { + 'click a.js-report-broken': this.switchMenu, + 'click a.js-report-files': this.switchMenu, + 'click a.js-report-orphaned-files': this.switchMenu, + 'click a.js-report-rules': this.switchMenu, + }, + ]; + }, + + switchMenu(event) { + const target = $(event.target); + if (!target.hasClass('active')) { + this.loading.set(true); + this.showFilesReport.set(false); + this.showBrokenCardsReport.set(false); + this.showOrphanedFilesReport.set(false); + if (this.subscription) { + this.subscription.stop(); + } + + $('.side-menu li.active').removeClass('active'); + target.parent().addClass('active'); + const targetID = target.data('id'); + + if ('report-broken' === targetID) { + this.showBrokenCardsReport.set(true); + this.subscription = Meteor.subscribe( + 'brokenCards', + SessionData.getSessionId(), + () => { + this.loading.set(false); + }, + ); + } else if ('report-files' === targetID) { + this.showFilesReport.set(true); + this.subscription = Meteor.subscribe('attachmentsList', () => { + this.loading.set(false); + }); + } else if ('report-orphaned-files' === targetID) { + this.showOrphanedFilesReport.set(true); + this.subscription = Meteor.subscribe('orphanedAttachments', () => { + this.loading.set(false); + }); + } else if ('report-rules' === targetID) { + this.subscription = Meteor.subscribe('rulesReport', () => { + this.showRulesReport.set(true); + this.loading.set(false); + }); + } + } + }, +}).register('adminReports'); + +Template.filesReport.helpers({ + attachmentFiles() { + // eslint-disable-next-line no-console + // console.log('attachments:', AttachmentStorage.find()); + // console.log('attachments.count:', AttachmentStorage.find().count()); + return AttachmentStorage.find(); + }, + + rulesReport() { + const rules = []; + + Rules.find().forEach(rule => { + rules.push({ + _id: rule._id, + title: rule.title, + boardId: rule.boardId, + boardTitle: rule.board().title, + action: rule.action().fetch(), + trigger: rule.trigger().fetch(), + }); + }); + + return rules; + }, + + resultsCount() { + return AttachmentStorage.find().count(); + }, + + fileSize(size) { + return Math.round(size / 1024); + }, + + usageCount(key) { + return Attachments.find({ 'copies.attachments.key': key }).count(); + }, +}); + +Template.orphanedFilesReport.helpers({ + attachmentFiles() { + // eslint-disable-next-line no-console + // console.log('attachments:', AttachmentStorage.find()); + // console.log('attachments.count:', AttachmentStorage.find().count()); + return AttachmentStorage.find(); + }, + + resultsCount() { + return AttachmentStorage.find().count(); + }, + + fileSize(size) { + return Math.round(size / 1024); + }, +}); + +Template.rulesReport.helpers({ + rows() { + const rules = []; + + Rules.find().forEach(rule => { + rules.push({ + _id: rule._id, + title: rule.title, + boardId: rule.boardId, + boardTitle: rule.board().title, + action: rule.action(), + trigger: rule.trigger(), + }); + }); + + console.log('rows:', rules); + return rules; + }, + + resultsCount() { + return Rules.find().count(); + }, +}); + +class BrokenCardsComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + } +} +BrokenCardsComponent.register('brokenCardsReport'); diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade index fef1067e3..09e850071 100644 --- a/client/components/settings/peopleBody.jade +++ b/client/components/settings/peopleBody.jade @@ -5,50 +5,201 @@ template(name="people") else .content-title.ext-box .ext-box-left - span - i.fa.fa-users - | {{_ 'people'}} - input#searchInput(placeholder="{{_ 'search'}}") - button#searchButton - i.fa.fa-search - | {{_ 'search'}} - .ext-box-right - span {{_ 'people-number'}} #{peopleNumber} + if loading.get + +spinner + else if orgSetting.get + span + i.fa.fa-sitemap + unless isMiniScreen + | {{_ 'organizations'}} + input#searchOrgInput(placeholder="{{_ 'search'}}") + button#searchOrgButton + i.fa.fa-search + | {{_ 'search'}} + .ext-box-right + span {{#unless isMiniScreen}}{{_ 'org-number'}}{{/unless}} #{orgNumber} + else if teamSetting.get + span + i.fa.fa-users + unless isMiniScreen + | {{_ 'teams'}} + input#searchTeamInput(placeholder="{{_ 'search'}}") + button#searchTeamButton + i.fa.fa-search + | {{_ 'search'}} + .ext-box-right + span {{#unless isMiniScreen}}{{_ 'team-number'}}{{/unless}} #{teamNumber} + else if peopleSetting.get + span + i.fa.fa-user + unless isMiniScreen + | {{_ 'people'}} + input#searchInput(placeholder="{{_ 'search'}}") + button#searchButton + i.fa.fa-search + | {{_ 'search'}} + .ext-box-right + span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber} .content-body .side-menu ul li.active - a.js-setting-menu(data-id="people-setting") + a.js-org-menu(data-id="org-setting") + i.fa.fa-sitemap + | {{_ 'organizations'}} + li + a.js-team-menu(data-id="team-setting") i.fa.fa-users + | {{_ 'teams'}} + li + a.js-people-menu(data-id="people-setting") + i.fa.fa-user | {{_ 'people'}} .main-body if loading.get +spinner - else if people.get + else if orgSetting.get + +orgGeneral + else if teamSetting.get + +teamGeneral + else if peopleSetting.get +peopleGeneral + +template(name="orgGeneral") + table + tbody + tr + th {{_ 'displayName'}} + th {{_ 'description'}} + th {{_ 'shortName'}} + th {{_ 'website'}} + th {{_ 'createdAt'}} + th {{_ 'active'}} + th + +newOrgRow + each org in orgList + +orgRow(orgId=org._id) + +template(name="teamGeneral") + table + tbody + tr + th {{_ 'displayName'}} + th {{_ 'description'}} + th {{_ 'shortName'}} + th {{_ 'website'}} + th {{_ 'createdAt'}} + th {{_ 'active'}} + th + +newTeamRow + each team in teamList + +teamRow(teamId=team._id) + template(name="peopleGeneral") table tbody tr th {{_ 'username'}} th {{_ 'fullname'}} + th {{_ 'initials'}} th {{_ 'admin'}} th {{_ 'email'}} th {{_ 'verified'}} th {{_ 'createdAt'}} th {{_ 'active'}} th {{_ 'authentication-method'}} + th {{_ 'import-usernames'}} + th {{_ 'organizations'}} + th {{_ 'teams'}} th +newUserRow each user in peopleList +peopleRow(userId=user._id) +template(name="newOrgRow") + a.new-org + i.fa.fa-plus-square + | {{_ 'new'}} + +template(name="newTeamRow") + a.new-team + i.fa.fa-plus-square + | {{_ 'new'}} + template(name="newUserRow") a.new-user - i.fa.fa-edit + i.fa.fa-plus-square | {{_ 'new'}} +template(name="orgRow") + tr + if orgData.orgIsActive + td {{ orgData.orgDisplayName }} + else + td {{ orgData.orgDisplayName }} + if orgData.orgIsActive + td {{ orgData.orgDesc }} + else + td {{ orgData.orgDesc }} + if orgData.orgIsActive + td {{ orgData.orgShortName }} + else + td {{ orgData.orgShortName }} + if orgData.orgIsActive + td {{ orgData.orgWebsite }} + else + td {{ orgData.orgWebsite }} + if orgData.orgIsActive + td {{ moment orgData.createdAt 'LLL' }} + else + td {{ moment orgData.createdAt 'LLL' }} + td + if orgData.orgIsActive + | {{_ 'no'}} + else + | {{_ 'yes'}} + td + a.edit-org + i.fa.fa-edit + | {{_ 'edit'}} + a.more-settings-org + i.fa.fa-ellipsis-h + +template(name="teamRow") + tr + if teamData.teamIsActive + td {{ teamData.teamDisplayName }} + else + td {{ teamData.teamDisplayName }} + if teamData.teamIsActive + td {{ teamData.teamDesc }} + else + td {{ teamData.teamDesc }} + if teamData.teamIsActive + td {{ teamData.teamShortName }} + else + td {{ teamData.teamShortName }} + if teamData.teamIsActive + td {{ teamData.teamWebsite }} + else + td {{ teamData.teamWebsite }} + if teamData.teamIsActive + td {{ moment teamData.createdAt 'LLL' }} + else + td {{ moment teamData.createdAt 'LLL' }} + td + if teamData.teamIsActive + | {{_ 'no'}} + else + | {{_ 'yes'}} + td + a.edit-team + i.fa.fa-edit + | {{_ 'edit'}} + a.more-settings-team + i.fa.fa-ellipsis-h + template(name="peopleRow") tr if userData.loginDisabled @@ -59,6 +210,10 @@ template(name="peopleRow") td {{ userData.profile.fullname }} else td {{ userData.profile.fullname }} + if userData.loginDisabled + td {{ userData.profile.initials }} + else + td {{ userData.profile.initials }} if userData.loginDisabled td if userData.isAdmin @@ -100,17 +255,80 @@ template(name="peopleRow") td {{_ userData.authenticationMethod }} else td {{_ userData.authenticationMethod }} + if userData.loginDisabled + td {{ userData.importUsernamesString }} + else + td {{ userData.importUsernamesString }} + if userData.loginDisabled + td {{ userData.orgsUserBelongs }} + else + td {{ userData.orgsUserBelongs }} + if userData.loginDisabled + td {{ userData.teamsUserBelongs }} + else + td {{ userData.teamsUserBelongs }} td a.edit-user i.fa.fa-edit | {{_ 'edit'}} + a.more-settings-user + i.fa.fa-ellipsis-h + +template(name="editOrgPopup") + form + label.hide.orgId(type="text" value=org._id) + label + | {{_ 'displayName'}} + input.js-orgDisplayName(type="text" value=org.orgDisplayName required) + span.error.hide.orgname-taken + | {{_ 'error-orgname-taken'}} + label + | {{_ 'description'}} + input.js-orgDesc(type="text" value=org.orgDesc required) + label + | {{_ 'shortName'}} + input.js-orgShortName(type="text" value=org.orgShortName required) + label + | {{_ 'website'}} + input.js-orgWebsite(type="text" value=org.orgWebsite required) + label + | {{_ 'active'}} + select.select-active.js-org-isactive + option(value="false") {{_ 'yes'}} + option(value="true" selected="{{org.orgIsActive}}") {{_ 'no'}} + hr + div.buttonsContainer + input.primary.wide(type="submit" value="{{_ 'save'}}") + +template(name="editTeamPopup") + form + label.hide.teamId(type="text" value=team._id) + label + | {{_ 'displayName'}} + input.js-teamDisplayName(type="text" value=team.teamDisplayName required) + span.error.hide.teamname-taken + | {{_ 'error-teamname-taken'}} + label + | {{_ 'description'}} + input.js-teamDesc(type="text" value=team.teamDesc required) + label + | {{_ 'shortName'}} + input.js-teamShortName(type="text" value=team.teamShortName required) + label + | {{_ 'website'}} + input.js-teamWebsite(type="text" value=team.teamWebsite required) + label + | {{_ 'active'}} + select.select-active.js-team-isactive + option(value="false") {{_ 'yes'}} + option(value="true" selected="{{team.teamIsActive}}") {{_ 'no'}} + hr + div.buttonsContainer + input.primary.wide(type="submit" value="{{_ 'save'}}") template(name="editUserPopup") form label.hide.userId(type="text" value=user._id) - label - | {{_ 'fullname'}} - input.js-profile-fullname(type="text" value=user.profile.fullname) label | {{_ 'username'}} span.error.hide.username-taken @@ -118,7 +336,18 @@ template(name="editUserPopup") if isLdap input.js-profile-username(type="text" value=user.username readonly) else - input.js-profile-username(type="text" value=user.username) + input.js-profile-username(type="text" value=user.username required) + label + | {{_ 'fullname'}} + input.js-profile-fullname(type="text" value=user.profile.fullname required) + label + | {{_ 'initials'}} + input.js-profile-initials(type="text" value=user.profile.initials required) + label + | {{_ 'admin'}} + select.select-role.js-profile-isadmin + option(value="false") {{_ 'no'}} + option(value="true" selected="{{user.isAdmin}}") {{_ 'yes'}} label | {{_ 'email'}} span.error.hide.email-taken @@ -126,12 +355,15 @@ template(name="editUserPopup") if isLdap input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly) else - input.js-profile-email(type="email" value="{{user.emails.[0].address}}") + input.js-profile-email(type="email" value="{{user.emails.[0].address}}" required) label - | {{_ 'admin'}} - select.select-role.js-profile-isadmin + | {{_ 'import-usernames'}} + input.js-import-usernames(type="text" value=user.importUsernames) + label + | {{_ 'verified'}} + select.select-verified.js-profile-email-verified option(value="false") {{_ 'no'}} - option(value="true" selected="{{user.isAdmin}}") {{_ 'yes'}} + option(value="true" selected="{{userData.emails.[0].verified}}") {{_ 'yes'}} label | {{_ 'active'}} select.select-active.js-profile-isactive @@ -145,21 +377,88 @@ template(name="editUserPopup") option(value="{{value}}" selected) {{_ value}} else option(value="{{value}}") {{_ value}} + label + | {{_ 'organizations'}} + i.fa.fa-plus-square#addUserOrg + i.fa.fa-minus-square#removeUserOrg + select.js-orgs#jsOrgs + option(value="-1") {{_ 'organizations'}} : + each value in orgsDatas + option(value="{{value._id}}") {{_ value.orgDisplayName}} + input#jsUserOrgsInPut.js-userOrgs(type="text" value=user.orgsUserBelongs, disabled) + input#jsUserOrgIdsInPut.js-userOrgIds.hide(type="text" value=user.orgIdsUserBelongs) + label + | {{_ 'teams'}} + i.fa.fa-plus-square#addUserTeam + i.fa.fa-minus-square#removeUserTeam + select.js-teams#jsTeams + option(value="-1") {{_ 'teams'}} : + each value in teamsDatas + option(value="{{value._id}}") {{_ value.teamDisplayName}} + input#jsUserTeamsInPut.js-userteams(type="text" value=user.teamsUserBelongs, disabled) + input#jsUserTeamIdsInPut.js-userteamIds.hide(type="text" value=user.teamIdsUserBelongs) + hr label | {{_ 'password'}} input.js-profile-password(type="password") div.buttonsContainer input.primary.wide(type="submit" value="{{_ 'save'}}") - // div - // input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}") + +template(name="newOrgPopup") + form + //label.hide.userId(type="text" value=user._id) + label + | {{_ 'displayName'}} + input.js-orgDisplayName(type="text" value="" required) + label + | {{_ 'description'}} + input.js-orgDesc(type="text" value="" required) + label + | {{_ 'shortName'}} + input.js-orgShortName(type="text" value="" required) + label + | {{_ 'website'}} + input.js-orgWebsite(type="text" value="" required) + label + | {{_ 'active'}} + select.select-active.js-org-isactive + option(value="false" selected="selected") {{_ 'yes'}} + option(value="true") {{_ 'no'}} + hr + div.buttonsContainer + input.primary.wide(type="submit" value="{{_ 'save'}}") + +template(name="newTeamPopup") + form + //label.hide.teamId(type="text" value=team._id) + label + | {{_ 'displayName'}} + input.js-teamDisplayName(type="text" value="" required) + label + | {{_ 'description'}} + input.js-teamDesc(type="text" value="" required) + label + | {{_ 'shortName'}} + input.js-teamShortName(type="text" value="" required) + label + | {{_ 'website'}} + input.js-teamWebsite(type="text" value="" required) + label + | {{_ 'active'}} + select.select-active.js-team-isactive + option(value="false" selected="selected") {{_ 'yes'}} + option(value="true") {{_ 'no'}} + hr + div.buttonsContainer + input.primary.wide(type="submit" value="{{_ 'save'}}") template(name="newUserPopup") form //label.hide.userId(type="text" value=user._id) label | {{_ 'fullname'}} - input.js-profile-fullname(type="text" value="") + input.js-profile-fullname(type="text" value="" required) label | {{_ 'username'}} span.error.hide.username-taken @@ -167,7 +466,10 @@ template(name="newUserPopup") //if isLdap // input.js-profile-username(type="text" value=user.username readonly) //else - input.js-profile-username(type="text" value="") + input.js-profile-username(type="text" value="" required) + label + | {{_ 'initials'}} + input.js-profile-initials(type="text" value="" required) label | {{_ 'email'}} span.error.hide.email-taken @@ -175,7 +477,10 @@ template(name="newUserPopup") //if isLdap // input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly) //else - input.js-profile-email(type="email" value="") + input.js-profile-email(type="email" value="" required) + label + | {{_ 'import-usernames'}} + input.js-import-usernames(type="text" value="") label | {{_ 'admin'}} select.select-role.js-profile-isadmin @@ -194,9 +499,91 @@ template(name="newUserPopup") option(value="{{value}}" selected) {{_ value}} else option(value="{{value}}") {{_ value}} + label + | {{_ 'organizations'}} + i.fa.fa-plus-square#addUserOrgNewUser + i.fa.fa-minus-square#removeUserOrgNewUser + select.js-orgsNewUser#jsOrgsNewUser + option(value="-1") {{_ 'organizations'}} : + each value in orgsDatas + option(value="{{value._id}}") {{_ value.orgDisplayName}} + input#jsUserOrgsInPutNewUser.js-userOrgsNewUser(type="text" value=user.orgsUserBelongs, disabled) + input#jsUserOrgIdsInPutNewUser.js-userOrgIdsNewUser.hide(type="text" value=user.orgIdsUserBelongs) + label + | {{_ 'teams'}} + i.fa.fa-plus-square#addUserTeamNewUser + i.fa.fa-minus-square#removeUserTeamNewUser + select.js-teamsNewUser#jsTeamsNewUser + option(value="-1") {{_ 'teams'}} : + each value in teamsDatas + option(value="{{value._id}}") {{_ value.teamDisplayName}} + input#jsUserTeamsInPutNewUser.js-userteamsNewUser(type="text" value=user.teamsUserBelongs, disabled) + input#jsUserTeamIdsInPutNewUser.js-userteamIdsNewUser.hide(type="text" value=user.teamIdsUserBelongs) + hr label | {{_ 'password'}} - input.js-profile-password(type="password") + input.js-profile-password(type="password" required) div.buttonsContainer input.primary.wide(type="submit" value="{{_ 'save'}}") + +template(name="settingsOrgPopup") + ul.pop-over-list + li + form + label#deleteOrgWarningMessage.hide + | {{_ 'delete-org-warning-message'}} + br + label + | {{_ 'delete-org-confirm-popup'}} + br + label.hide.orgId(type="text" value=org._id) + labeldelete-org-confirm-popup + div.buttonsContainer + input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}") + // It's not yet possible to impersonate organization. Only impersonate user, + // because that changes current user ID. What would it mean in practice + // to impersonate organization? + // li + // a.impersonate-org + // i.fa.fa-user + // | {{_ 'impersonate-org'}} + // + // + +template(name="settingsTeamPopup") + ul.pop-over-list + li + form + label#deleteTeamWarningMessage.hide + | {{_ 'delete-team-warning-message'}} + br + label + | {{_ 'delete-team-confirm-popup'}} + br + label.hide.teamId(type="text" value=team._id) + div.buttonsContainer + input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}") + +template(name="settingsUserPopup") + ul.pop-over-list + li + a.impersonate-user + i.fa.fa-user + | {{_ 'impersonate-user'}} + hr + li + form + label.hide.userId(type="text" value=user._id) + div.buttonsContainer + input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}") + // Delete is enabled, but there is still bug of leaving empty user avatars + // to boards: boards members, card members and assignees have + // empty users. So it is better to remove user from all boards before removing user. + // See: + // - wekan/client/components/settings/peopleBody.jade deleteButton + // - wekan/client/components/settings/peopleBody.js deleteButton + // - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember' + // that does now remove member from board, card members and assignees correctly, + // but that should be used to remove user from all boards similarly + // - wekan/models/users.js Delete is not enabled diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js index 186afd581..ab6d1fbd2 100644 --- a/client/components/settings/peopleBody.js +++ b/client/components/settings/peopleBody.js @@ -1,4 +1,7 @@ +const orgsPerPage = 25; +const teamsPerPage = 25; const usersPerPage = 25; +let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user BlazeComponent.extendComponent({ mixins() { @@ -7,17 +10,45 @@ BlazeComponent.extendComponent({ onCreated() { this.error = new ReactiveVar(''); this.loading = new ReactiveVar(false); - this.people = new ReactiveVar(true); + this.orgSetting = new ReactiveVar(true); + this.teamSetting = new ReactiveVar(true); + this.peopleSetting = new ReactiveVar(true); + this.findOrgsOptions = new ReactiveVar({}); + this.findTeamsOptions = new ReactiveVar({}); this.findUsersOptions = new ReactiveVar({}); - this.number = new ReactiveVar(0); + this.numberOrgs = new ReactiveVar(0); + this.numberTeams = new ReactiveVar(0); + this.numberPeople = new ReactiveVar(0); this.page = new ReactiveVar(1); this.loadNextPageLocked = false; this.callFirstWith(null, 'resetNextPeak'); this.autorun(() => { - const limit = this.page.get() * usersPerPage; + const limitOrgs = this.page.get() * orgsPerPage; + const limitTeams = this.page.get() * teamsPerPage; + const limitUsers = this.page.get() * usersPerPage; - this.subscribe('people', this.findUsersOptions.get(), limit, () => { + this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => { + this.loadNextPageLocked = false; + const nextPeakBefore = this.callFirstWith(null, 'getNextPeak'); + this.calculateNextPeak(); + const nextPeakAfter = this.callFirstWith(null, 'getNextPeak'); + if (nextPeakBefore === nextPeakAfter) { + this.callFirstWith(null, 'resetNextPeak'); + } + }); + + this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => { + this.loadNextPageLocked = false; + const nextPeakBefore = this.callFirstWith(null, 'getNextPeak'); + this.calculateNextPeak(); + const nextPeakAfter = this.callFirstWith(null, 'getNextPeak'); + if (nextPeakBefore === nextPeakAfter) { + this.callFirstWith(null, 'resetNextPeak'); + } + }); + + this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => { this.loadNextPageLocked = false; const nextPeakBefore = this.callFirstWith(null, 'getNextPeak'); this.calculateNextPeak(); @@ -31,6 +62,22 @@ BlazeComponent.extendComponent({ events() { return [ { + 'click #searchOrgButton'() { + this.filterOrg(); + }, + 'keydown #searchOrgInput'(event) { + if (event.keyCode === 13 && !event.shiftKey) { + this.filterOrg(); + } + }, + 'click #searchTeamButton'() { + this.filterTeam(); + }, + 'keydown #searchTeamInput'(event) { + if (event.keyCode === 13 && !event.shiftKey) { + this.filterTeam(); + } + }, 'click #searchButton'() { this.filterPeople(); }, @@ -39,16 +86,23 @@ BlazeComponent.extendComponent({ this.filterPeople(); } }, + 'click #newOrgButton'() { + Popup.open('newOrg'); + }, + 'click #newTeamButton'() { + Popup.open('newTeam'); + }, 'click #newUserButton'() { Popup.open('newUser'); }, + 'click a.js-org-menu': this.switchMenu, + 'click a.js-team-menu': this.switchMenu, + 'click a.js-people-menu': this.switchMenu, }, ]; }, filterPeople() { - const value = $('#searchInput') - .first() - .val(); + const value = $('#searchInput').first().val(); if (value === '') { this.findUsersOptions.set({}); } else { @@ -84,18 +138,63 @@ BlazeComponent.extendComponent({ setLoading(w) { this.loading.set(w); }, + orgList() { + const orgs = Org.find(this.findOrgsOptions.get(), { + fields: { _id: true }, + }); + this.numberOrgs.set(orgs.count(false)); + return orgs; + }, + teamList() { + const teams = Team.find(this.findTeamsOptions.get(), { + fields: { _id: true }, + }); + this.numberTeams.set(teams.count(false)); + return teams; + }, peopleList() { const users = Users.find(this.findUsersOptions.get(), { fields: { _id: true }, }); - this.number.set(users.count(false)); + this.numberPeople.set(users.count(false)); return users; }, + orgNumber() { + return this.numberOrgs.get(); + }, + teamNumber() { + return this.numberTeams.get(); + }, peopleNumber() { - return this.number.get(); + return this.numberPeople.get(); + }, + switchMenu(event) { + const target = $(event.target); + if (!target.hasClass('active')) { + $('.side-menu li.active').removeClass('active'); + target.parent().addClass('active'); + const targetID = target.data('id'); + this.orgSetting.set('org-setting' === targetID); + this.teamSetting.set('team-setting' === targetID); + this.peopleSetting.set('people-setting' === targetID); + } }, }).register('people'); +Template.orgRow.helpers({ + orgData() { + const orgCollection = this.esSearch ? ESSearchResults : Org; + return orgCollection.findOne(this.orgId); + }, +}); + +Template.teamRow.helpers({ + teamData() { + const teamCollection = this.esSearch ? ESSearchResults : Team; + return teamCollection.findOne(this.teamId); + }, +}); + Template.peopleRow.helpers({ userData() { const userCollection = this.esSearch ? ESSearchResults : Users; @@ -103,7 +202,7 @@ Template.peopleRow.helpers({ }, }); -Template.editUserPopup.onCreated(function() { +Template.editUserPopup.onCreated(function () { this.authenticationMethods = new ReactiveVar([]); this.errorMessage = new ReactiveVar(''); @@ -115,13 +214,31 @@ Template.editUserPopup.onCreated(function() { { value: 'password' }, // Gets only the authentication methods availables ...Object.entries(result) - .filter(e => e[1]) - .map(e => ({ value: e[0] })), + .filter((e) => e[1]) + .map((e) => ({ value: e[0] })), ]); } }); }); +Template.editOrgPopup.helpers({ + org() { + return Org.findOne(this.orgId); + }, + errorMessage() { + return Template.instance().errorMessage.get(); + }, +}); + +Template.editTeamPopup.helpers({ + team() { + return Team.findOne(this.teamId); + }, + errorMessage() { + return Template.instance().errorMessage.get(); + }, +}); + Template.editUserPopup.helpers({ user() { return Users.findOne(this.userId); @@ -129,6 +246,12 @@ Template.editUserPopup.helpers({ authentications() { return Template.instance().authenticationMethods.get(); }, + orgsDatas() { + return Org.find({}, {sort: { createdAt: -1 }}); + }, + teamsDatas() { + return Team.find({}, {sort: { createdAt: -1 }}); + }, isSelected(match) { const userId = Template.instance().data.userId; const selected = Users.findOne(userId).authenticationMethod; @@ -144,7 +267,15 @@ Template.editUserPopup.helpers({ }, }); -Template.newUserPopup.onCreated(function() { +Template.newOrgPopup.onCreated(function () { + this.errorMessage = new ReactiveVar(''); +}); + +Template.newTeamPopup.onCreated(function () { + this.errorMessage = new ReactiveVar(''); +}); + +Template.newUserPopup.onCreated(function () { this.authenticationMethods = new ReactiveVar([]); this.errorMessage = new ReactiveVar(''); @@ -156,35 +287,94 @@ Template.newUserPopup.onCreated(function() { { value: 'password' }, // Gets only the authentication methods availables ...Object.entries(result) - .filter(e => e[1]) - .map(e => ({ value: e[0] })), + .filter((e) => e[1]) + .map((e) => ({ value: e[0] })), ]); } }); }); -Template.newUserPopup.helpers({ - //user() { - // return Users.findOne(this.userId); - //}, - authentications() { - return Template.instance().authenticationMethods.get(); +Template.newOrgPopup.helpers({ + org() { + return Org.findOne(this.orgId); }, - //isSelected(match) { - // const userId = Template.instance().data.userId; - // const selected = Users.findOne(userId).authenticationMethod; - // return selected === match; - //}, - //isLdap() { - // const userId = Template.instance().data.userId; - // const selected = Users.findOne(userId).authenticationMethod; - // return selected === 'ldap'; - //}, errorMessage() { return Template.instance().errorMessage.get(); }, }); +Template.newTeamPopup.helpers({ + team() { + return Team.findOne(this.teamId); + }, + errorMessage() { + return Template.instance().errorMessage.get(); + }, +}); + +Template.newUserPopup.helpers({ + user() { + return Users.findOne(this.userId); + }, + authentications() { + return Template.instance().authenticationMethods.get(); + }, + orgsDatas() { + return Org.find({}, {sort: { createdAt: -1 }}); + }, + teamsDatas() { + return Team.find({}, {sort: { createdAt: -1 }}); + }, + isSelected(match) { + const userId = Template.instance().data.userId; + if(userId){ + const selected = Users.findOne(userId).authenticationMethod; + return selected === match; + } + else{ + false; + } + }, + isLdap() { + const userId = Template.instance().data.userId; + const selected = Users.findOne(userId).authenticationMethod; + return selected === 'ldap'; + }, + errorMessage() { + return Template.instance().errorMessage.get(); + }, +}); + +BlazeComponent.extendComponent({ + onCreated() {}, + org() { + return Org.findOne(this.orgId); + }, + events() { + return [ + { + 'click a.edit-org': Popup.open('editOrg'), + 'click a.more-settings-org': Popup.open('settingsOrg'), + }, + ]; + }, +}).register('orgRow'); + +BlazeComponent.extendComponent({ + onCreated() {}, + team() { + return Team.findOne(this.teamId); + }, + events() { + return [ + { + 'click a.edit-team': Popup.open('editTeam'), + 'click a.more-settings-team': Popup.open('settingsTeam'), + }, + ]; + }, +}).register('teamRow'); + BlazeComponent.extendComponent({ onCreated() {}, user() { @@ -194,11 +384,32 @@ BlazeComponent.extendComponent({ return [ { 'click a.edit-user': Popup.open('editUser'), + 'click a.more-settings-user': Popup.open('settingsUser'), }, ]; }, }).register('peopleRow'); +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click a.new-org': Popup.open('newOrg'), + }, + ]; + }, +}).register('newOrgRow'); + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click a.new-team': Popup.open('newTeam'), + }, + ]; + }, +}).register('newTeamRow'); + BlazeComponent.extendComponent({ events() { return [ @@ -209,24 +420,123 @@ BlazeComponent.extendComponent({ }, }).register('newUserRow'); +Template.editOrgPopup.events({ + submit(event, templateInstance) { + event.preventDefault(); + const org = Org.findOne(this.orgId); + + const orgDisplayName = templateInstance + .find('.js-orgDisplayName') + .value.trim(); + const orgDesc = templateInstance.find('.js-orgDesc').value.trim(); + const orgShortName = templateInstance.find('.js-orgShortName').value.trim(); + const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim(); + const orgIsActive = + templateInstance.find('.js-org-isactive').value.trim() == 'true'; + + const isChangeOrgDisplayName = orgDisplayName !== org.orgDisplayName; + const isChangeOrgDesc = orgDesc !== org.orgDesc; + const isChangeOrgShortName = orgShortName !== org.orgShortName; + const isChangeOrgWebsite = orgWebsite !== org.orgWebsite; + const isChangeOrgIsActive = orgIsActive !== org.orgIsActive; + + if ( + isChangeOrgDisplayName || + isChangeOrgDesc || + isChangeOrgShortName || + isChangeOrgWebsite || + isChangeOrgIsActive + ) { + Meteor.call( + 'setOrgAllFields', + org, + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + ); + } + + Popup.close(); + }, +}); + +Template.editTeamPopup.events({ + submit(event, templateInstance) { + event.preventDefault(); + const team = Team.findOne(this.teamId); + + const teamDisplayName = templateInstance + .find('.js-teamDisplayName') + .value.trim(); + const teamDesc = templateInstance.find('.js-teamDesc').value.trim(); + const teamShortName = templateInstance + .find('.js-teamShortName') + .value.trim(); + const teamWebsite = templateInstance.find('.js-teamWebsite').value.trim(); + const teamIsActive = + templateInstance.find('.js-team-isactive').value.trim() == 'true'; + + const isChangeTeamDisplayName = teamDisplayName !== team.teamDisplayName; + const isChangeTeamDesc = teamDesc !== team.teamDesc; + const isChangeTeamShortName = teamShortName !== team.teamShortName; + const isChangeTeamWebsite = teamWebsite !== team.teamWebsite; + const isChangeTeamIsActive = teamIsActive !== team.teamIsActive; + + if ( + isChangeTeamDisplayName || + isChangeTeamDesc || + isChangeTeamShortName || + isChangeTeamWebsite || + isChangeTeamIsActive + ) { + Meteor.call( + 'setTeamAllFields', + team, + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + ); + } + + Popup.close(); + }, +}); + Template.editUserPopup.events({ submit(event, templateInstance) { event.preventDefault(); const user = Users.findOne(this.userId); - const fullname = templateInstance.find('.js-profile-fullname').value.trim(); const username = templateInstance.find('.js-profile-username').value.trim(); + const fullname = templateInstance.find('.js-profile-fullname').value.trim(); + const initials = templateInstance.find('.js-profile-initials').value.trim(); const password = templateInstance.find('.js-profile-password').value; const isAdmin = templateInstance.find('.js-profile-isadmin').value.trim(); const isActive = templateInstance.find('.js-profile-isactive').value.trim(); const email = templateInstance.find('.js-profile-email').value.trim(); - const authentication = templateInstance - .find('.js-authenticationMethod') - .value.trim(); + const verified = templateInstance.find('.js-profile-email-verified').value.trim(); + const authentication = templateInstance.find('.js-authenticationMethod').value.trim(); + const importUsernames = templateInstance.find('.js-import-usernames').value.trim(); + const userOrgs = templateInstance.find('.js-userOrgs').value.trim(); + const userOrgsIds = templateInstance.find('.js-userOrgIds').value.trim(); + const userTeams = templateInstance.find('.js-userteams').value.trim(); + const userTeamsIds = templateInstance.find('.js-userteamIds').value.trim(); const isChangePassword = password.length > 0; const isChangeUserName = username !== user.username; + const isChangeInitials = initials.length > 0; + const isChangeEmailVerified = verified !== user.emails[0].verified; + + // If previously email address has not been set, it is undefined, + // check for undefined, and allow adding email address. const isChangeEmail = - email.toLowerCase() !== user.emails[0].address.toLowerCase(); + email.toLowerCase() !== + (typeof user.emails !== 'undefined' + ? user.emails[0].address.toLowerCase() + : false); Users.update(this.userId, { $set: { @@ -234,20 +544,65 @@ Template.editUserPopup.events({ isAdmin: isAdmin === 'true', loginDisabled: isActive === 'true', authenticationMethod: authentication, + importUsernames: Users.parseImportUsernames(importUsernames), }, }); + let userTeamsList = userTeams.split(","); + let userTeamsIdsList = userTeamsIds.split(","); + let userTms = []; + if(userTeams != ''){ + for(let i = 0; i < userTeamsList.length; i++){ + userTms.push({ + "teamId": userTeamsIdsList[i], + "teamDisplayName": userTeamsList[i], + }) + } + } + + Users.update(this.userId, { + $set:{ + teams: userTms + } + }); + + let userOrgsList = userOrgs.split(","); + let userOrgsIdsList = userOrgsIds.split(","); + let userOrganizations = []; + if(userOrgs != ''){ + for(let i = 0; i < userOrgsList.length; i++){ + userOrganizations.push({ + "orgId": userOrgsIdsList[i], + "orgDisplayName": userOrgsList[i], + }) + } + } + + Users.update(this.userId, { + $set:{ + orgs: userOrganizations + } + }); + if (isChangePassword) { Meteor.call('setPassword', password, this.userId); } + if (isChangeEmailVerified) { + Meteor.call('setEmailVerified', email, verified === 'true', this.userId); + } + + if (isChangeInitials) { + Meteor.call('setInitials', initials, this.userId); + } + if (isChangeUserName && isChangeEmail) { Meteor.call( 'setUsernameAndEmail', username, email.toLowerCase(), this.userId, - function(error) { + function (error) { const usernameMessageElement = templateInstance.$('.username-taken'); const emailMessageElement = templateInstance.$('.email-taken'); if (error) { @@ -267,7 +622,7 @@ Template.editUserPopup.events({ }, ); } else if (isChangeUserName) { - Meteor.call('setUsername', username, this.userId, function(error) { + Meteor.call('setUsername', username, this.userId, function (error) { const usernameMessageElement = templateInstance.$('.username-taken'); if (error) { const errorElement = error.error; @@ -280,27 +635,186 @@ Template.editUserPopup.events({ } }); } else if (isChangeEmail) { - Meteor.call('setEmail', email.toLowerCase(), this.userId, function( - error, - ) { - const emailMessageElement = templateInstance.$('.email-taken'); - if (error) { - const errorElement = error.error; - if (errorElement === 'email-already-taken') { - emailMessageElement.show(); + Meteor.call( + 'setEmail', + email.toLowerCase(), + this.userId, + function (error) { + const emailMessageElement = templateInstance.$('.email-taken'); + if (error) { + const errorElement = error.error; + if (errorElement === 'email-already-taken') { + emailMessageElement.show(); + } + } else { + emailMessageElement.hide(); + Popup.close(); } - } else { - emailMessageElement.hide(); - Popup.close(); - } - }); + }, + ); } else Popup.close(); }, + 'click #addUserOrg'(event) { + event.preventDefault(); - 'click #deleteButton': Popup.afterConfirm('userDelete', function() { - Users.remove(this.userId); + userOrgsTeamsAction = "addOrg"; + document.getElementById("jsOrgs").style.display = 'block'; + document.getElementById("jsTeams").style.display = 'none'; + }, + 'click #removeUserOrg'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "removeOrg"; + document.getElementById("jsOrgs").style.display = 'block'; + document.getElementById("jsTeams").style.display = 'none'; + }, + 'click #addUserTeam'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "addTeam"; + document.getElementById("jsTeams").style.display = 'block'; + document.getElementById("jsOrgs").style.display = 'none'; + }, + 'click #removeUserTeam'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "removeTeam"; + document.getElementById("jsTeams").style.display = 'block'; + document.getElementById("jsOrgs").style.display = 'none'; + }, + 'change #jsOrgs'(event) { + event.preventDefault(); + UpdateUserOrgsOrTeamsElement(); + }, + 'change #jsTeams'(event) { + event.preventDefault(); + UpdateUserOrgsOrTeamsElement(); + }, +}); + +UpdateUserOrgsOrTeamsElement = function(isNewUser = false){ + let selectedElt; + let selectedEltValue; + let selectedEltValueId; + let inputElt; + let inputEltId; + let lstInputValues = []; + let lstInputValuesIds = []; + let index; + let indexId; + switch(userOrgsTeamsAction) + { + case "addOrg": + case "removeOrg": + inputElt = !isNewUser ? document.getElementById("jsUserOrgsInPut") : document.getElementById("jsUserOrgsInPutNewUser"); + inputEltId = !isNewUser ? document.getElementById("jsUserOrgIdsInPut") : document.getElementById("jsUserOrgIdsInPutNewUser"); + selectedElt = !isNewUser ? document.getElementById("jsOrgs") : document.getElementById("jsOrgsNewUser"); + break; + case "addTeam": + case "removeTeam": + inputElt = !isNewUser ? document.getElementById("jsUserTeamsInPut") : document.getElementById("jsUserTeamsInPutNewUser"); + inputEltId = !isNewUser ? document.getElementById("jsUserTeamIdsInPut") : document.getElementById("jsUserTeamIdsInPutNewUser"); + selectedElt = !isNewUser ? document.getElementById("jsTeams") : document.getElementById("jsTeamsNewUser"); + break; + default: + break; + } + selectedEltValue = selectedElt.options[selectedElt.selectedIndex].text; + selectedEltValueId = selectedElt.options[selectedElt.selectedIndex].value; + lstInputValues = inputElt.value.trim().split(","); + if(lstInputValues.length == 1 && lstInputValues[0] == ''){ + lstInputValues = []; + } + lstInputValuesIds = inputEltId.value.trim().split(","); + if(lstInputValuesIds.length == 1 && lstInputValuesIds[0] == ''){ + lstInputValuesIds = []; + } + index = lstInputValues.indexOf(selectedEltValue); + indexId = lstInputValuesIds.indexOf(selectedEltValueId); + if(userOrgsTeamsAction == "addOrg" || userOrgsTeamsAction == "addTeam"){ + if(index <= -1 && selectedEltValueId != "-1"){ + lstInputValues.push(selectedEltValue); + } + + if(indexId <= -1 && selectedEltValueId != "-1"){ + lstInputValuesIds.push(selectedEltValueId); + } + } + else{ + if(index > -1 && selectedEltValueId != "-1"){ + lstInputValues.splice(index, 1); + } + + if(indexId > -1 && selectedEltValueId != "-1"){ + lstInputValuesIds.splice(indexId, 1); + } + } + + if(lstInputValues.length > 0){ + inputElt.value = lstInputValues.join(","); + } + else{ + inputElt.value = ""; + } + + if(lstInputValuesIds.length > 0){ + inputEltId.value = lstInputValuesIds.join(","); + } + else{ + inputEltId.value = ""; + } + selectedElt.value = "-1"; + selectedElt.style.display = "none"; +} + +Template.newOrgPopup.events({ + submit(event, templateInstance) { + event.preventDefault(); + const orgDisplayName = templateInstance + .find('.js-orgDisplayName') + .value.trim(); + const orgDesc = templateInstance.find('.js-orgDesc').value.trim(); + const orgShortName = templateInstance.find('.js-orgShortName').value.trim(); + const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim(); + const orgIsActive = + templateInstance.find('.js-org-isactive').value.trim() == 'true'; + + Meteor.call( + 'setCreateOrg', + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + ); Popup.close(); - }), + }, +}); + +Template.newTeamPopup.events({ + submit(event, templateInstance) { + event.preventDefault(); + const teamDisplayName = templateInstance + .find('.js-teamDisplayName') + .value.trim(); + const teamDesc = templateInstance.find('.js-teamDesc').value.trim(); + const teamShortName = templateInstance + .find('.js-teamShortName') + .value.trim(); + const teamWebsite = templateInstance.find('.js-teamWebsite').value.trim(); + const teamIsActive = + templateInstance.find('.js-team-isactive').value.trim() == 'true'; + + Meteor.call( + 'setCreateTeam', + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + ); + Popup.close(); + }, }); Template.newUserPopup.events({ @@ -308,19 +822,51 @@ Template.newUserPopup.events({ event.preventDefault(); const fullname = templateInstance.find('.js-profile-fullname').value.trim(); const username = templateInstance.find('.js-profile-username').value.trim(); + const initials = templateInstance.find('.js-profile-initials').value.trim(); const password = templateInstance.find('.js-profile-password').value; const isAdmin = templateInstance.find('.js-profile-isadmin').value.trim(); const isActive = templateInstance.find('.js-profile-isactive').value.trim(); const email = templateInstance.find('.js-profile-email').value.trim(); + const importUsernames = Users.parseImportUsernames( + templateInstance.find('.js-import-usernames').value, + ); + const userOrgs = templateInstance.find('.js-userOrgsNewUser').value.trim(); + const userOrgsIds = templateInstance.find('.js-userOrgIdsNewUser').value.trim(); + const userTeams = templateInstance.find('.js-userteamsNewUser').value.trim(); + const userTeamsIds = templateInstance.find('.js-userteamIdsNewUser').value.trim(); + + let userTeamsList = userTeams.split(","); + let userTeamsIdsList = userTeamsIds.split(","); + let userTms = []; + for(let i = 0; i < userTeamsList.length; i++){ + userTms.push({ + "teamId": userTeamsIdsList[i], + "teamDisplayName": userTeamsList[i], + }) + } + + let userOrgsList = userOrgs.split(","); + let userOrgsIdsList = userOrgsIds.split(","); + let userOrganizations = []; + for(let i = 0; i < userOrgsList.length; i++){ + userOrganizations.push({ + "orgId": userOrgsIdsList[i], + "orgDisplayName": userOrgsList[i], + }) + } Meteor.call( 'setCreateUser', fullname, username, + initials, password, isAdmin, isActive, email.toLowerCase(), + importUsernames, + userOrganizations, + userTms, function(error) { const usernameMessageElement = templateInstance.$('.username-taken'); const emailMessageElement = templateInstance.$('.email-taken'); @@ -342,4 +888,128 @@ Template.newUserPopup.events({ ); Popup.close(); }, + 'click #addUserOrgNewUser'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "addOrg"; + document.getElementById("jsOrgsNewUser").style.display = 'block'; + document.getElementById("jsTeamsNewUser").style.display = 'none'; + }, + 'click #removeUserOrgNewUser'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "removeOrg"; + document.getElementById("jsOrgsNewUser").style.display = 'block'; + document.getElementById("jsTeamsNewUser").style.display = 'none'; + }, + 'click #addUserTeamNewUser'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "addTeam"; + document.getElementById("jsTeamsNewUser").style.display = 'block'; + document.getElementById("jsOrgsNewUser").style.display = 'none'; + }, + 'click #removeUserTeamNewUser'(event) { + event.preventDefault(); + + userOrgsTeamsAction = "removeTeam"; + document.getElementById("jsTeamsNewUser").style.display = 'block'; + document.getElementById("jsOrgsNewUser").style.display = 'none'; + }, + 'change #jsOrgsNewUser'(event) { + event.preventDefault(); + UpdateUserOrgsOrTeamsElement(true); + }, + 'change #jsTeamsNewUser'(event) { + event.preventDefault(); + UpdateUserOrgsOrTeamsElement(true); + }, +}); + +Template.settingsOrgPopup.events({ + 'click #deleteButton'(event) { + event.preventDefault(); + if(Users.find({"orgs.orgId": this.orgId}).count() > 0) + { + let orgClassList = document.getElementById("deleteOrgWarningMessage").classList; + if(orgClassList.contains('hide')) + { + orgClassList.remove('hide'); + document.getElementById("deleteOrgWarningMessage").style.color = "red"; + } + return; + } + Org.remove(this.orgId); + Popup.close(); + } +}); + +Template.settingsTeamPopup.events({ + 'click #deleteButton'(event) { + event.preventDefault(); + if(Users.find({"teams.teamId": this.teamId}).count() > 0) + { + let teamClassList = document.getElementById("deleteTeamWarningMessage").classList; + if(teamClassList.contains('hide')) + { + teamClassList.remove('hide'); + document.getElementById("deleteTeamWarningMessage").style.color = "red"; + } + return; + } + Team.remove(this.teamId); + Popup.close(); + } +}); + +Template.settingsUserPopup.events({ + 'click .impersonate-user'(event) { + event.preventDefault(); + + Meteor.call('impersonate', this.userId, (err) => { + if (!err) { + FlowRouter.go('/'); + Meteor.connection.setUserId(this.userId); + } + }); + }, + 'click #deleteButton'(event) { + event.preventDefault(); + /* + // Delete is not enabled yet, because it does leave empty user avatars + // to boards: boards members, card members and assignees have + // empty users. See: + // - wekan/client/components/settings/peopleBody.jade deleteButton + // - wekan/client/components/settings/peopleBody.js deleteButton + // - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember' + // that does now remove member from board, card members and assignees correctly, + // but that should be used to remove user from all boards similarly + // - wekan/models/users.js Delete is not enabled + // + //Users.remove(this.userId); + */ + Popup.close(); + }, +}); + +Template.settingsUserPopup.helpers({ + user() { + return Users.findOne(this.userId); + }, + authentications() { + return Template.instance().authenticationMethods.get(); + }, + isSelected(match) { + const userId = Template.instance().data.userId; + const selected = Users.findOne(userId).authenticationMethod; + return selected === match; + }, + isLdap() { + const userId = Template.instance().data.userId; + const selected = Users.findOne(userId).authenticationMethod; + return selected === 'ldap'; + }, + errorMessage() { + return Template.instance().errorMessage.get(); + }, }); diff --git a/client/components/settings/peopleBody.styl b/client/components/settings/peopleBody.styl index c223e181f..8ef33c9e0 100644 --- a/client/components/settings/peopleBody.styl +++ b/client/components/settings/peopleBody.styl @@ -21,7 +21,7 @@ table .ext-box-left display: flex; - width: 40% + width: 100% span vertical-align: center; @@ -46,3 +46,12 @@ table div margin: auto + +.more-settings-user,.more-settings-team,.more-settings-org + margin-left: 10px; + +.js-orgs,.js-orgsNewUser + display: none; + +.js-teams,.js-teamsNewUser + display: none; diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade index 835a3b814..76f8ae95c 100644 --- a/client/components/settings/settingBody.jade +++ b/client/components/settings/settingBody.jade @@ -13,10 +13,11 @@ template(name="setting") a.js-setting-menu(data-id="registration-setting") i.fa.fa-sign-in | {{_ 'registration'}} - li - a.js-setting-menu(data-id="email-setting") - i.fa.fa-envelope - | {{_ 'email'}} + unless isSandstorm + li + a.js-setting-menu(data-id="email-setting") + i.fa.fa-envelope + | {{_ 'email'}} li a.js-setting-menu(data-id="account-setting") i.fa.fa-users @@ -39,7 +40,8 @@ template(name="setting") else if generalSetting.get +general else if emailSetting.get - +email + unless isSandstorm + +email else if accountSetting.get +accountSettings else if announcementSetting.get @@ -80,45 +82,48 @@ template(name="general") template(name='email') ul#email-setting.setting-detail - li.smtp-form - .title {{_ 'smtp-host'}} - .description {{_ 'smtp-host-description'}} - .form-group - input.wekan-form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}") - li.smtp-form - .title {{_ 'smtp-port'}} - .description {{_ 'smtp-port-description'}} - .form-group - input.wekan-form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") - li.smtp-form - .title {{_ 'smtp-username'}} - .form-group - input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}") - li.smtp-form - .title {{_ 'smtp-password'}} - .form-group - input.wekan-form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}") - li.smtp-form - .title {{_ 'smtp-tls'}} - .form-group - a.flex.js-toggle-tls - .materialCheckBox#mail-server-tls(class="{{#if currentSetting.mailServer.enableTLS}}is-checked{{/if}}") - - span {{_ 'smtp-tls-description'}} - - li.smtp-form - .title {{_ 'send-from'}} - .form-group - input.wekan-form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}") - - li - button.js-save.primary {{_ 'save'}} + //if isSandstorm + // li.smtp-form + // .title {{_ 'smtp-host'}} + // .description {{_ 'smtp-host-description'}} + // .form-group + // input.wekan-form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}") + // li.smtp-form + // .title {{_ 'smtp-port'}} + // .description {{_ 'smtp-port-description'}} + // .form-group + // input.wekan-form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") + // li.smtp-form + // .title {{_ 'smtp-username'}} + // .form-group + // input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}") + // li.smtp-form + // .title {{_ 'smtp-password'}} + // .form-group + // input.wekan-form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="") + // li.smtp-form + // .title {{_ 'smtp-tls'}} + // .form-group + // a.flex.js-toggle-tls + // .materialCheckBox#mail-server-tls(class="{{#if currentSetting.mailServer.enableTLS}}is-checked{{/if}}") + // + // span {{_ 'smtp-tls-description'}} + // + // li.smtp-form + // .title {{_ 'send-from'}} + // .form-group + // input.wekan-form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}") + // + // li + // button.js-save.primary {{_ 'save'}} li button.js-send-smtp-test-email.primary {{_ 'send-smtp-test'}} template(name='accountSettings') ul#account-setting.setting-detail + li + button.js-all-hide-system-messages.primary {{_ 'hide-system-messages-of-all-users'}} li.accounts-form .title {{_ 'accounts-allowEmailChange'}} .form-group.flex @@ -126,23 +131,18 @@ template(name='accountSettings') span {{_ 'yes'}} input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}") span {{_ 'no'}} - li - li.accounts-form .title {{_ 'accounts-allowUserNameChange'}} .form-group.flex input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}") span {{_ 'yes'}} input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}") span {{_ 'no'}} - li - li.accounts-form .title {{_ 'accounts-allowUserDelete'}} .form-group.flex input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="true" checked="{{#if allowUserDelete}}checked{{/if}}") span {{_ 'yes'}} input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="false" checked="{{#unless allowUserDelete}}checked{{/unless}}") span {{_ 'no'}} - li button.js-accounts-save.primary {{_ 'save'}} template(name='announcementSettings') @@ -163,13 +163,6 @@ template(name='announcementSettings') template(name='layoutSettings') ul#layout-setting.setting-detail - //li.layout-form - .title {{_ 'hide-logo'}} - .form-group.flex - input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}") - span {{_ 'yes'}} - input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}") - span {{_ 'no'}} li.layout-form .title {{_ 'display-authentication-method'}} .form-group.flex @@ -180,14 +173,51 @@ template(name='layoutSettings') li.layout-form .title {{_ 'default-authentication-method'}} +selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod) + li.layout-form + .title {{_ 'wait-spinner'}} + +selectSpinnerName(spinnerName=currentSetting.spinnerName) li.layout-form .title {{_ 'custom-product-name'}} .form-group input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}") + li.layout-form + .title {{_ 'hide-logo'}} + .form-group.flex + input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}") + span {{_ 'yes'}} + input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}") + span {{_ 'no'}} + li.layout-form + .title {{_ 'custom-login-logo-image-url'}} + .form-group + input.wekan-form-control#custom-login-logo-image-url(type="text", placeholder="" value="{{currentSetting.customLoginLogoImageUrl}}") + li.layout-form + .title {{_ 'custom-login-logo-link-url'}} + .form-group + input.wekan-form-control#custom-login-logo-link-url(type="text", placeholder="" value="{{currentSetting.customLoginLogoLinkUrl}}") + li.layout-form + .title {{_ 'text-below-custom-login-logo'}} + .form-group + textarea#text-below-custom-login-logo.wekan-form-control= currentSetting.textBelowCustomLoginLogo + li.layout-form + .title {{_ 'custom-top-left-corner-logo-image-url'}} + .form-group + input.wekan-form-control#custom-top-left-corner-logo-image-url(type="text", placeholder="" value="{{currentSetting.customTopLeftCornerLogoImageUrl}}") + li.layout-form + .title {{_ 'custom-top-left-corner-logo-link-url'}} + .form-group + input.wekan-form-control#custom-top-left-corner-logo-link-url(type="text", placeholder="" value="{{currentSetting.customTopLeftCornerLogoLinkUrl}}") + li.layout-form + .title {{_ 'custom-top-left-corner-logo-height'}} + .form-group + input.wekan-form-control#custom-top-left-corner-logo-height(type="text", placeholder="" value="{{currentSetting.customTopLeftCornerLogoHeight}}") + li.layout-form + .title {{_ 'automatic-linked-url-schemes'}} + .form-group + textarea#automatic-linked-url-schemes.wekan-form-control= currentSetting.automaticLinkedUrlSchemes li button.js-save-layout.primary {{_ 'save'}} - template(name='selectAuthenticationMethod') select#defaultAuthenticationMethod each authentications @@ -195,3 +225,11 @@ template(name='selectAuthenticationMethod') option(value="{{value}}" selected) {{_ value}} else option(value="{{value}}") {{_ value}} + +template(name='selectSpinnerName') + select#spinnerName + each spinner in spinners + if isSelected spinner + option(value="{{spinner}}" selected) {{_ spinner}} + else + option(value="{{spinner}}") {{_ spinner}} diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 319c066b2..e55e2db6b 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -1,3 +1,5 @@ +import { ALLOWED_WAIT_SPINNERS } from '/config/const'; + BlazeComponent.extendComponent({ onCreated() { this.error = new ReactiveVar(''); @@ -48,7 +50,7 @@ BlazeComponent.extendComponent({ 'members.isAdmin': true, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, @@ -167,18 +169,55 @@ BlazeComponent.extendComponent({ const productName = $('#product-name') .val() .trim(); + const customLoginLogoImageUrl = $('#custom-login-logo-image-url') + .val() + .trim(); + const customLoginLogoLinkUrl = $('#custom-login-logo-link-url') + .val() + .trim(); + const textBelowCustomLoginLogo = $('#text-below-custom-login-logo') + .val() + .trim(); + const automaticLinkedUrlSchemes = $('#automatic-linked-url-schemes') + .val() + .trim(); + const customTopLeftCornerLogoImageUrl = $( + '#custom-top-left-corner-logo-image-url', + ) + .val() + .trim(); + const customTopLeftCornerLogoLinkUrl = $( + '#custom-top-left-corner-logo-link-url', + ) + .val() + .trim(); + const customTopLeftCornerLogoHeight = $( + '#custom-top-left-corner-logo-height', + ) + .val() + .trim(); const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true'; const displayAuthenticationMethod = $('input[name=displayAuthenticationMethod]:checked').val() === 'true'; const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val(); + const spinnerName = $('#spinnerName').val(); + try { Settings.update(Settings.findOne()._id, { $set: { productName, hideLogo: hideLogoChange, + customLoginLogoImageUrl, + customLoginLogoLinkUrl, + textBelowCustomLoginLogo, + customTopLeftCornerLogoImageUrl, + customTopLeftCornerLogoLinkUrl, + customTopLeftCornerLogoHeight, displayAuthenticationMethod, defaultAuthenticationMethod, + automaticLinkedUrlSchemes, + spinnerName, }, }); } catch (e) { @@ -240,7 +279,6 @@ BlazeComponent.extendComponent({ $set: { booleanValue: allowUserDelete }, }); }, - allowEmailChange() { return AccountSettings.findOne('accounts-allowEmailChange').booleanValue; }, @@ -250,12 +288,31 @@ BlazeComponent.extendComponent({ allowUserDelete() { return AccountSettings.findOne('accounts-allowUserDelete').booleanValue; }, + allHideSystemMessages() { + Meteor.call('setAllUsersHideSystemMessages', (err, ret) => { + if (!err && ret) { + if (ret === true) { + const message = `${TAPi18n.__( + 'now-system-messages-of-all-users-are-hidden', + )}`; + alert(message); + } + } else { + const reason = err.reason || ''; + const message = `${TAPi18n.__(err.error)}\n${reason}`; + alert(message); + } + }); + }, events() { return [ { 'click button.js-accounts-save': this.saveAccountsChange, }, + { + 'click button.js-all-hide-system-messages': this.allHideSystemMessages, + }, ]; }, }).register('accountSettings'); @@ -332,3 +389,12 @@ Template.selectAuthenticationMethod.helpers({ return Template.instance().data.authenticationMethod === match; }, }); + +Template.selectSpinnerName.helpers({ + spinners() { + return ALLOWED_WAIT_SPINNERS; + }, + isSelected(match) { + return Template.instance().data.spinnerName === match; + }, +}); diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade index 221c1b793..14de3ab1f 100644 --- a/client/components/settings/settingHeader.jade +++ b/client/components/settings/settingHeader.jade @@ -3,22 +3,25 @@ template(name="settingHeaderBar") span {{_ 'admin-panel'}} .setting-header-btns.left - unless isMiniScreen - if currentUser - a.setting-header-btn.settings(href="{{pathFor 'setting'}}") - i.fa(class="fa-cog") - span {{_ 'settings'}} + if currentUser + a.setting-header-btn.settings(href="{{pathFor 'setting'}}") + i.fa(class="fa-cog") + span {{_ 'settings'}} - a.setting-header-btn.people(href="{{pathFor 'people'}}") - i.fa(class="fa-users") - span {{_ 'people'}} + a.setting-header-btn.people(href="{{pathFor 'people'}}") + i.fa(class="fa-users") + span {{_ 'people'}} - a.setting-header-btn.informations(href="{{pathFor 'information'}}") - i.fa(class="fa-info-circle") - span {{_ 'info'}} + a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}") + i.fa(class="fa-list") + span {{_ 'reports'}} - else - a.setting-header-btn.js-log-in( - title="{{_ 'log-in'}}") - i.fa.fa-sign-in - span {{_ 'log-in'}} + a.setting-header-btn.informations(href="{{pathFor 'information'}}") + i.fa(class="fa-info-circle") + span {{_ 'info'}} + + else + a.setting-header-btn.js-log-in( + title="{{_ 'log-in'}}") + i.fa.fa-sign-in + span {{_ 'log-in'}} diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 901fe99f9..9401ecc90 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -5,11 +5,7 @@ template(name="sidebar") // title="{{showTongueTitle}}") // i.fa.fa-navicon .sidebar-shadow - .sidebar-content.sidebar-shortcuts - a.board-header-btn.js-shortcuts - i.fa.fa-keyboard-o - span {{_ 'keyboard-shortcuts' }} - .sidebar-content.js-board-sidebar-content.js-perfect-scrollbar + .sidebar-content.js-board-sidebar-content //a.hide-btn.js-hide-sidebar // i.fa.fa-navicon unless isDefaultView @@ -23,7 +19,7 @@ template(name='homeSidebar') hr +labelsWidget ul#cards.label-text-hidden - a.flex.js-toggle-minicard-label-text + a.flex.js-toggle-minicard-label-text(title="{{_ 'hide-minicard-label-text'}}") span {{_ 'hide-minicard-label-text'}} b   .materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}") @@ -35,10 +31,36 @@ template(name='homeSidebar') +activities(mode="board") template(name="membersWidget") + .board-widget.board-widget-members + h3 + i.fa.fa-users + | {{_ 'organizations'}} + + .board-widget-content + +boardOrgGeneral + .clearfix + br + hr + .board-widget.board-widget-members + h3 + i.fa.fa-users + | {{_ 'teams'}} + + .board-widget-content + +boardTeamGeneral + .clearfix + br + hr .board-widget.board-widget-members h3 i.fa.fa-users | {{_ 'members'}} + + .sidebar-shortcuts + a.board-header-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}") + i.fa.fa-keyboard-o + span {{_ 'keyboard-shortcuts' }} + unless currentUser.isCommentOnly unless currentUser.isWorker a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right @@ -49,10 +71,10 @@ template(name="membersWidget") +userAvatar(userId=this.userId showStatus=true) if isSandstorm if currentUser.isBoardMember - a.member.add-member.sandstorm-powerbox-request-identity + a.member.add-member.sandstorm-powerbox-request-identity(title="{{_ 'add-members'}}") i.fa.fa-plus else if currentUser.isBoardAdmin - a.member.add-member.js-manage-board-members + a.member.add-member.js-manage-board-members(title="{{_ 'add-members'}}") i.fa.fa-plus .clearfix if isInvited @@ -63,6 +85,30 @@ template(name="membersWidget") button.js-member-invite-accept.primary {{_ 'accept'}} button.js-member-invite-decline {{_ 'decline'}} +template(name="boardOrgGeneral") + table + tbody + tr + th {{_ 'displayName'}} + th + if currentUser.isBoardAdmin + a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}") + i.fa.fa-plus + each org in currentBoard.activeOrgs + +boardOrgRow(orgId=org.orgId) + +template(name="boardTeamGeneral") + table + tbody + tr + th {{_ 'displayName'}} + th + if currentUser.isBoardAdmin + a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}") + i.fa.fa-plus + each currentBoard.activeTeams + +boardTeamRow(teamId=this.teamId) + template(name="boardChangeColorPopup") .board-backgrounds-list.clearfix each backgroundColors @@ -105,6 +151,14 @@ template(name="boardCardSettingsPopup") span i.fa.fa-users | {{_ 'members'}} + + div.check-div + a.flex.js-field-has-creator(class="{{#if allowsCreator}}is-checked{{/if}}") + .materialCheckBox(class="{{#if allowsCreator}}is-checked{{/if}}") + span + i.fa.fa-user + | {{_ 'creator'}} + div.check-div a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}") @@ -123,6 +177,12 @@ template(name="boardCardSettingsPopup") span i.fa.fa-user-plus | {{_ 'requested-by'}} + div.check-div + a.flex.js-field-has-card-sorting-by-number(class="{{#if allowsCardSortingByNumber}}is-checked{{/if}}") + .materialCheckBox(class="{{#if allowsCardSortingByNumber}}is-checked{{/if}}") + span + i.fa.fa-sort + | {{_ 'card-sorting-by-number'}} div.check-div a.flex.js-field-has-labels(class="{{#if allowsLabels}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsLabels}}is-checked{{/if}}") @@ -230,6 +290,8 @@ template(name="chooseBoardSource") a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}} li a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}} + li + a(href="{{pathFor '/import/csv'}}") {{_ 'from-csv'}} template(name="archiveBoardPopup") p {{_ 'close-board-pop'}} @@ -267,14 +329,16 @@ template(name="outgoingWebhooksPopup") template(name="boardMenuPopup") ul.pop-over-list - li - a.js-open-rules-view(title="{{_ 'rules'}}") - i.fa.fa-magic - | {{_ 'rules'}} - li - a.js-custom-fields - i.fa.fa-list-alt - | {{_ 'custom-fields'}} + if currentUser.isBoardAdmin + li + a.js-open-rules-view(title="{{_ 'rules'}}") + i.fa.fa-magic + | {{_ 'rules'}} + if currentUser.isBoardAdmin + li + a.js-custom-fields + i.fa.fa-list-alt + | {{_ 'custom-fields'}} li a.js-open-archives i.fa.fa-archive @@ -284,79 +348,58 @@ template(name="boardMenuPopup") a.js-change-board-color i.fa.fa-paint-brush | {{_ 'board-change-color'}} - - //- - XXX Language should be handled by sandstorm, but for now display a - language selection link in the board menu. This link is normally present - in the header bar that is not displayed on sandstorm. - if isSandstorm + hr + ul.pop-over-list + if withApi li - a.js-change-language - i.fa.fa-flag - | {{_ 'language'}} - unless isSandstorm - if currentUser.isBoardAdmin - hr - ul.pop-over-list - li - a(href="{{exportUrl}}", download="{{exportFilename}}") - i.fa.fa-share-alt - | {{_ 'export-board'}} - li - a.js-outgoing-webhooks - i.fa.fa-globe - | {{_ 'outgoing-webhooks'}} - li - a.js-card-settings - i.fa.fa-id-card-o - | {{_ 'card-settings'}} - li - a.js-subtask-settings - i.fa.fa-sitemap - | {{_ 'subtask-settings'}} - unless currentBoard.isTemplatesBoard - hr - ul.pop-over-list - li - a.js-archive-board - i.fa.fa-arrow-right - i.fa.fa-archive - | {{_ 'archive-board'}} - - if isSandstorm - hr - ul.pop-over-list - li - a(href="{{exportUrl}}", download="{{exportFilename}}") + a.js-export-board i.fa.fa-share-alt - i.fa.fa-sign-out | {{_ 'export-board'}} - li - a.js-import-board - i.fa.fa-share-alt - i.fa.fa-sign-in - | {{_ 'import-board-c'}} - li - a.js-archive-board - i.fa.fa-arrow-right - i.fa.fa-archive - | {{_ 'archive-board'}} + if currentUser.isBoardAdmin li a.js-outgoing-webhooks i.fa.fa-globe | {{_ 'outgoing-webhooks'}} - hr - ul.pop-over-list li a.js-card-settings i.fa.fa-id-card-o | {{_ 'card-settings'}} - hr - ul.pop-over-list li a.js-subtask-settings i.fa.fa-sitemap | {{_ 'subtask-settings'}} + unless currentBoard.isTemplatesBoard + if currentUser.isBoardAdmin + hr + ul.pop-over-list + li + a.js-archive-board + i.fa.fa-arrow-right + i.fa.fa-archive + | {{_ 'archive-board'}} + +template(name="exportBoard") + ul.pop-over-list + li + a.download-json-link(href="{{exportUrl}}", download="{{exportJsonFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-json'}} + li + a(href="{{exportUrlExcel}}", download="{{exportFilenameExcel}}") + i.fa.fa-share-alt + | {{_ 'export-board-excel'}} + li + a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-csv'}} + li + a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-tsv'}} + li + a.html-export-board + i.fa.fa-archive + | {{_ 'export-board-html'}} template(name="labelsWidget") .board-widget.board-widget-labels @@ -371,7 +414,7 @@ template(name="labelsWidget") +viewer = name if currentUser.isBoardAdmin - a.card-label.add-label.js-add-label + a.card-label.add-label.js-add-label(title="{{_ 'label-create'}}") i.fa.fa-plus template(name="memberPopup") @@ -410,6 +453,40 @@ template(name="leaveBoardPopup") p {{_ 'leave-board-pop' boardTitle=board.title}} button.js-confirm.negate.full(type="submit") {{_ 'leave-board'}} +template(name="addBoardOrgPopup") + select.js-boardOrgs#jsBoardOrgs + option(value="-1") {{_ 'organizations'}} : + each value in orgsDatas + option(value="{{value._id}}") {{_ value.orgDisplayName}} + +template(name="removeBoardOrgPopup") + form + input.hide#hideOrgId(type="text" value=org._id) + label + | {{_ 'leave-board'}} ? + br + hr + div.buttonsContainer + input.primary.wide.leaveBoardBtn#leaveBoardBtn(type="submit" value="{{_ 'leave-board'}}") + input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardBtn(type="submit" value="{{_ 'Cancel'}}") + +template(name="addBoardTeamPopup") + select.js-boardTeams#jsBoardTeams + option(value="-1") {{_ 'teams'}} : + each value in teamsDatas + option(value="{{value._id}}") {{_ value.teamDisplayName}} + +template(name="removeBoardTeamPopup") + form + input.hide#hideTeamId(type="text" value=team._id) + label + | {{_ 'leave-board'}} ? + br + hr + div.buttonsContainer + input.primary.wide.leaveBoardBtn#leaveBoardTeamBtn(type="submit" value="{{_ 'leave-board'}}") + input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardTeamBtn(type="submit" value="{{_ 'Cancel'}}") + template(name="addMemberPopup") .js-search-member +esInput(index="users") diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index baf571142..3f693f897 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,5 +1,3 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); Sidebar = null; const defaultView = 'home'; @@ -16,7 +14,7 @@ const viewTitles = { BlazeComponent.extendComponent({ mixins() { - return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; + return [Mixins.InfiniteScrolling]; }, onCreated() { @@ -112,10 +110,12 @@ BlazeComponent.extendComponent({ currentUser = Meteor.user(); if (currentUser) { Meteor.call('toggleMinicardLabelText'); - } else if (cookies.has('hiddenMinicardLabelText')) { - cookies.remove('hiddenMinicardLabelText'); + } else if (window.localStorage.getItem('hiddenMinicardLabelText')) { + window.localStorage.removeItem('hiddenMinicardLabelText'); + location.reload(); } else { - cookies.set('hiddenMinicardLabelText', 'true'); + window.localStorage.setItem('hiddenMinicardLabelText', 'true'); + location.reload(); } }, 'click .js-shortcuts'() { @@ -133,7 +133,7 @@ Template.homeSidebar.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).hiddenMinicardLabelText; - } else if (cookies.has('hiddenMinicardLabelText')) { + } else if (window.localStorage.getItem('hiddenMinicardLabelText')) { return true; } else { return false; @@ -155,6 +155,9 @@ Template.memberPopup.helpers({ user() { return Users.findOne(this.userId); }, + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, memberType() { const type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal'; if (type === 'normal') { @@ -163,16 +166,16 @@ Template.memberPopup.helpers({ const noComments = currentBoard.hasNoComments(this.userId); const worker = currentBoard.hasWorker(this.userId); if (commentOnly) { - return TAPi18n.__('comment-only').toLowerCase(); + return TAPi18n.__('comment-only'); } else if (noComments) { - return TAPi18n.__('no-comments').toLowerCase(); + return TAPi18n.__('no-comments'); } else if (worker) { - return TAPi18n.__('worker').toLowerCase(); + return TAPi18n.__('worker'); } else { - return TAPi18n.__(type).toLowerCase(); + return TAPi18n.__(type); } } else { - return TAPi18n.__(type).toLowerCase(); + return TAPi18n.__(type); } }, isInvited() { @@ -213,9 +216,23 @@ Template.boardMenuPopup.events({ 'click .js-import-board': Popup.open('chooseBoardSource'), 'click .js-subtask-settings': Popup.open('boardSubtaskSettings'), 'click .js-card-settings': Popup.open('boardCardSettings'), + 'click .js-export-board': Popup.open('exportBoard'), +}); + +Template.boardMenuPopup.onCreated(function() { + this.apiEnabled = new ReactiveVar(false); + Meteor.call('_isApiEnabled', (e, result) => { + this.apiEnabled.set(result); + }); }); Template.boardMenuPopup.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + withApi() { + return Template.instance().apiEnabled.get(); + }, exportUrl() { const params = { boardId: Session.get('currentBoard'), @@ -227,7 +244,7 @@ Template.boardMenuPopup.helpers({ }, exportFilename() { const boardId = Session.get('currentBoard'); - return `wekan-export-board-${boardId}.json`; + return `export-board-${boardId}.json`; }, }); @@ -238,11 +255,15 @@ Template.memberPopup.events({ }, 'click .js-change-role': Popup.open('changePermissions'), 'click .js-remove-member': Popup.afterConfirm('removeMember', function() { + // This works from removing member from board, card members and assignees. const boardId = Session.get('currentBoard'); const memberId = this.userId; Cards.find({ boardId, members: memberId }).forEach(card => { card.unassignMember(memberId); }); + Cards.find({ boardId, assignees: memberId }).forEach(card => { + card.unassignAssignee(memberId); + }); Boards.findOne(boardId).removeMember(memberId); Popup.close(); }), @@ -283,12 +304,17 @@ Template.membersWidget.helpers({ return false; } }, + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, }); Template.membersWidget.events({ 'click .js-member': Popup.open('member'), 'click .js-open-board-menu': Popup.open('boardMenu'), 'click .js-manage-board-members': Popup.open('addMember'), + 'click .js-manage-board-addOrg': Popup.open('addBoardOrg'), + 'click .js-manage-board-addTeam': Popup.open('addBoardTeam'), 'click .js-import': Popup.open('boardImportBoard'), submit: this.onSubmit, 'click .js-import-board': Popup.open('chooseBoardSource'), @@ -395,11 +421,98 @@ BlazeComponent.extendComponent({ }, }).register('chooseBoardSourcePopup'); +BlazeComponent.extendComponent({ + template() { + return 'exportBoard'; + }, + withApi() { + return Template.instance().apiEnabled.get(); + }, + exportUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path('/api/boards/:boardId/export', params, queryParams); + }, + exportUrlExcel() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path( + '/api/boards/:boardId/exportExcel', + params, + queryParams, + ); + }, + exportFilenameExcel() { + const boardId = Session.get('currentBoard'); + return `export-board-excel-${boardId}.xlsx`; + }, + exportCsvUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path( + '/api/boards/:boardId/export/csv', + params, + queryParams, + ); + }, + exportTsvUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + delimiter: '\t', + }; + return FlowRouter.path( + '/api/boards/:boardId/export/csv', + params, + queryParams, + ); + }, + exportJsonFilename() { + const boardId = Session.get('currentBoard'); + return `export-board-${boardId}.json`; + }, + exportCsvFilename() { + const boardId = Session.get('currentBoard'); + return `export-board-${boardId}.csv`; + }, + exportTsvFilename() { + const boardId = Session.get('currentBoard'); + return `export-board-${boardId}.tsv`; + }, +}).register('exportBoardPopup'); + +Template.exportBoard.events({ + 'click .html-export-board': async event => { + event.preventDefault(); + await ExportHtml(Popup)(); + }, +}); + Template.labelsWidget.events({ 'click .js-label': Popup.open('editLabel'), 'click .js-add-label': Popup.open('createLabel'), }); +Template.labelsWidget.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, +}); + // Board members can assign people or labels by drag-dropping elements from the // sidebar to the cards on the board. In order to re-initialize the jquery-ui // plugin any time a draggable member or label is modified or removed we use a @@ -499,7 +612,7 @@ BlazeComponent.extendComponent({ 'members.userId': Meteor.userId(), }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, @@ -619,6 +732,14 @@ BlazeComponent.extendComponent({ return this.currentBoard.allowsSubtasks; }, + allowsCreator() { + return ( + this.currentBoard.allowsCreator === null || + this.currentBoard.allowsCreator === undefined || + this.currentBoard.allowsCreator + ); + }, + allowsMembers() { return this.currentBoard.allowsMembers; }, @@ -635,6 +756,10 @@ BlazeComponent.extendComponent({ return this.currentBoard.allowsRequestedBy; }, + allowsCardSortingByNumber() { + return this.currentBoard.allowsCardSortingByNumber; + }, + allowsLabels() { return this.currentBoard.allowsLabels; }, @@ -677,7 +802,7 @@ BlazeComponent.extendComponent({ 'members.userId': Meteor.userId(), }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ); }, @@ -778,6 +903,19 @@ BlazeComponent.extendComponent({ this.currentBoard.allowsSubtasks, ); }, + 'click .js-field-has-creator'(evt) { + evt.preventDefault(); + this.currentBoard.allowsCreator = !this.currentBoard.allowsCreator; + this.currentBoard.setAllowsCreator(this.currentBoard.allowsCreator); + $(`.js-field-has-creator ${MCB}`).toggleClass( + CKCLS, + this.currentBoard.allowsCreator, + ); + $('.js-field-has-creator').toggleClass( + CKCLS, + this.currentBoard.allowsCreator, + ); + }, 'click .js-field-has-members'(evt) { evt.preventDefault(); this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers; @@ -836,6 +974,22 @@ BlazeComponent.extendComponent({ this.currentBoard.allowsRequestedBy, ); }, + 'click .js-field-has-card-sorting-by-number'(evt) { + evt.preventDefault(); + this.currentBoard.allowsCardSortingByNumber = !this.currentBoard + .allowsCardSortingByNumber; + this.currentBoard.setAllowsCardSortingByNumber( + this.currentBoard.allowsCardSortingByNumber, + ); + $(`.js-field-has-card-sorting-by-number ${MCB}`).toggleClass( + CKCLS, + this.currentBoard.allowsCardSortingByNumber, + ); + $('.js-field-has-card-sorting-by-number').toggleClass( + CKCLS, + this.currentBoard.allowsCardSortingByNumber, + ); + }, 'click .js-field-has-labels'(evt) { evt.preventDefault(); this.currentBoard.allowsLabels = !this.currentBoard.allowsLabels; @@ -1016,6 +1170,283 @@ BlazeComponent.extendComponent({ }, }).register('addMemberPopup'); +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'change #jsBoardOrgs'() { + let currentBoard = Boards.findOne(Session.get('currentBoard')); + let selectElt = document.getElementById("jsBoardOrgs"); + let selectedOrgId = selectElt.options[selectElt.selectedIndex].value; + let selectedOrgDisplayName = selectElt.options[selectElt.selectedIndex].text; + let boardOrganizations = []; + if(currentBoard.orgs !== undefined){ + for(let i = 0; i < currentBoard.orgs.length; i++){ + boardOrganizations.push(currentBoard.orgs[i]); + } + } + + if(!boardOrganizations.some((org) => org.orgDisplayName == selectedOrgDisplayName)){ + boardOrganizations.push({ + "orgId": selectedOrgId, + "orgDisplayName": selectedOrgDisplayName, + "isActive" : true, + }) + + if (selectedOrgId != "-1") { + Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id); + } + } + + Popup.close(); + }, + }, + ]; + }, +}).register('addBoardOrgPopup'); + +Template.addBoardOrgPopup.helpers({ + orgsDatas() { + // return Org.find({}, {sort: { createdAt: -1 }}); + let orgs = Org.find({}, {sort: { createdAt: -1 }}); + return orgs; + }, +}); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'click #leaveBoardBtn'(){ + let stringOrgId = document.getElementById('hideOrgId').value; + let currentBoard = Boards.findOne(Session.get('currentBoard')); + let boardOrganizations = []; + if(currentBoard.orgs !== undefined){ + for(let i = 0; i < currentBoard.orgs.length; i++){ + if(currentBoard.orgs[i].orgId != stringOrgId){ + boardOrganizations.push(currentBoard.orgs[i]); + } + } + } + + Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id); + + Popup.close(); + }, + 'click #cancelLeaveBoardBtn'(){ + Popup.close(); + }, + }, + ]; + }, +}).register('removeBoardOrgPopup'); + +Template.removeBoardOrgPopup.helpers({ + org() { + return Org.findOne(this.orgId); + }, +}); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'change #jsBoardTeams'() { + let currentBoard = Boards.findOne(Session.get('currentBoard')); + let selectElt = document.getElementById("jsBoardTeams"); + let selectedTeamId = selectElt.options[selectElt.selectedIndex].value; + let selectedTeamDisplayName = selectElt.options[selectElt.selectedIndex].text; + let boardTeams = []; + if(currentBoard.teams !== undefined){ + for(let i = 0; i < currentBoard.teams.length; i++){ + boardTeams.push(currentBoard.teams[i]); + } + } + + if(!boardTeams.some((team) => team.teamDisplayName == selectedTeamDisplayName)){ + boardTeams.push({ + "teamId": selectedTeamId, + "teamDisplayName": selectedTeamDisplayName, + "isActive" : true, + }) + + if (selectedTeamId != "-1") { + Meteor.call('setBoardTeams', boardTeams, currentBoard._id); + } + } + + Popup.close(); + }, + }, + ]; + }, +}).register('addBoardTeamPopup'); + +Template.addBoardTeamPopup.helpers({ + teamsDatas() { + let teams = Team.find({}, {sort: { createdAt: -1 }}); + return teams; + }, +}); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'click #leaveBoardTeamBtn'(){ + let stringTeamId = document.getElementById('hideTeamId').value; + let currentBoard = Boards.findOne(Session.get('currentBoard')); + let boardTeams = []; + if(currentBoard.teams !== undefined){ + for(let i = 0; i < currentBoard.teams.length; i++){ + if(currentBoard.teams[i].teamId != stringTeamId){ + boardTeams.push(currentBoard.teams[i]); + } + } + } + + Meteor.call('setBoardTeams', boardTeams, currentBoard._id); + + Popup.close(); + }, + 'click #cancelLeaveBoardTeamBtn'(){ + Popup.close(); + }, + }, + ]; + }, +}).register('removeBoardTeamPopup'); + +Template.removeBoardTeamPopup.helpers({ + team() { + return Team.findOne(this.teamId); + }, +}); + Template.changePermissionsPopup.events({ 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'( event, diff --git a/client/components/sidebar/sidebar.styl b/client/components/sidebar/sidebar.styl index c10472771..2570083b0 100644 --- a/client/components/sidebar/sidebar.styl +++ b/client/components/sidebar/sidebar.styl @@ -34,6 +34,7 @@ color: darken(white, 50%) font-size: 1em margin-bottom: 10px + font-weight: bold i.fa margin-right: 3px @@ -99,13 +100,12 @@ margin-right: 10px .sidebar-shortcuts - margin: 0 + position: absolute + margin-left: 40% padding: 0 - top: auto - text-align: center + top: 7px font-size: 0.8em line-height: 1.6em - vertical-align: middle color: darken(white, 40%) .board-sidebar @@ -167,8 +167,8 @@ button float: right margin: 0 - margin-bottom: 5px - padding: 0 2px 0 10px + margin: 0 0 5px 5px + padding: 0 10px 0 10px @media screen and (max-width: 800px) .board-sidebar @@ -214,3 +214,13 @@ i.fa padding: 8px 0px 8px 16px font-weight: bold + +#jsBoardOrgs, #jsBoardTeams + width: 90% + +.leaveBoardBtn + background-color: green !important + +.cancelLeaveBoardBtn + margin-left: 5% !important + background-color: red !important diff --git a/client/components/sidebar/sidebarArchives.jade b/client/components/sidebar/sidebarArchives.jade index 56423ad76..66d1cde6c 100644 --- a/client/components/sidebar/sidebarArchives.jade +++ b/client/components/sidebar/sidebarArchives.jade @@ -5,17 +5,24 @@ template(name="archivesSidebar") unless isWorker p.quiet a.js-restore-all-cards {{_ 'restore-all'}} - | - - a.js-delete-all-cards {{_ 'delete-all'}} + if currentUser.isBoardAdmin + | - + a.js-delete-all-cards {{_ 'delete-all'}} each archivedCards .minicard-wrapper.js-minicard +minicard(this) if currentUser.isBoardMember unless isWorker p.quiet + if this.archivedAt + | {{_ 'archived-at' }} + | + | {{ moment this.archivedAt 'LLL' }} + br a.js-restore-card {{_ 'restore'}} - | - - a.js-delete-card {{_ 'delete'}} + if currentUser.isBoardAdmin + | - + a.js-delete-card {{_ 'delete'}} if cardIsInArchivedList p.quiet.small ({{_ 'warn-list-archived'}}) else @@ -25,8 +32,9 @@ template(name="archivesSidebar") unless isWorker p.quiet a.js-restore-all-lists {{_ 'restore-all'}} - | - - a.js-delete-all-lists {{_ 'delete-all'}} + if currentUser.isBoardAdmin + | - + a.js-delete-all-lists {{_ 'delete-all'}} ul.archived-lists each archivedLists li.archived-lists-item @@ -34,9 +42,15 @@ template(name="archivesSidebar") if currentUser.isBoardMember unless isWorker p.quiet + if this.archivedAt + | {{_ 'archived-at' }} + | + | {{ moment this.archivedAt 'LLL' }} + br a.js-restore-list {{_ 'restore'}} - | - - a.js-delete-list {{_ 'delete'}} + if currentUser.isBoardAdmin + | - + a.js-delete-list {{_ 'delete'}} else li.no-items-message {{_ 'no-archived-lists'}} @@ -44,8 +58,9 @@ template(name="archivesSidebar") unless isWorker p.quiet a.js-restore-all-swimlanes {{_ 'restore-all'}} - | - - a.js-delete-all-swimlanes {{_ 'delete-all'}} + if currentUser.isBoardAdmin + | - + a.js-delete-all-swimlanes {{_ 'delete-all'}} ul.archived-lists each archivedSwimlanes li.archived-lists-item @@ -53,9 +68,15 @@ template(name="archivesSidebar") if currentUser.isBoardMember unless isWorker p.quiet + if this.archivedAt + | {{_ 'archived-at' }} + | + | {{ moment this.archivedAt 'LLL' }} + br a.js-restore-swimlane {{_ 'restore'}} - | - - a.js-delete-swimlane {{_ 'delete'}} + if currentUser.isBoardAdmin + | - + a.js-delete-swimlane {{_ 'delete'}} else li.no-items-message {{_ 'no-archived-swimlanes'}} else diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js index 75b694e9e..daddd464f 100644 --- a/client/components/sidebar/sidebarArchives.js +++ b/client/components/sidebar/sidebarArchives.js @@ -31,24 +31,39 @@ BlazeComponent.extendComponent({ }, archivedCards() { - return Cards.find({ - archived: true, - boardId: Session.get('currentBoard'), - }); + return Cards.find( + { + archived: true, + boardId: Session.get('currentBoard'), + }, + { + sort: { archivedAt: -1, modifiedAt: -1 }, + }, + ); }, archivedLists() { - return Lists.find({ - archived: true, - boardId: Session.get('currentBoard'), - }); + return Lists.find( + { + archived: true, + boardId: Session.get('currentBoard'), + }, + { + sort: { archivedAt: -1, modifiedAt: -1 }, + }, + ); }, archivedSwimlanes() { - return Swimlanes.find({ - archived: true, - boardId: Session.get('currentBoard'), - }); + return Swimlanes.find( + { + archived: true, + boardId: Session.get('currentBoard'), + }, + { + sort: { archivedAt: -1, modifiedAt: -1 }, + }, + ); }, cardIsInArchivedList() { @@ -141,6 +156,9 @@ BlazeComponent.extendComponent({ }).register('archivesSidebar'); Template.archivesSidebar.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, isWorker() { const currentBoard = Boards.findOne(Session.get('currentBoard')); return ( diff --git a/client/components/sidebar/sidebarCustomFields.jade b/client/components/sidebar/sidebarCustomFields.jade index 4d023f452..d1970ef1a 100644 --- a/client/components/sidebar/sidebarCustomFields.jade +++ b/client/components/sidebar/sidebarCustomFields.jade @@ -33,12 +33,32 @@ template(name="createCustomFieldPopup") option(value=value selected="selected") {{name}} else option(value=value) {{name}} + + div.js-field-settings.js-field-settings-currency(class="{{#if isTypeNotSelected 'currency'}}hide{{/if}}") + label + | {{_ 'custom-field-currency-option'}} + select.js-field-currency + each getCurrencyCodes + if selected + option(value=value selected="selected") {{name}} + else + option(value=value) {{name}} + div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}") label | {{_ 'custom-field-dropdown-options'}} each dropdownItems.get input.js-dropdown-item(type="text" value=name placeholder="") input.js-dropdown-item.last(type="text" value="" placeholder="{{_ 'custom-field-dropdown-options-placeholder'}}") + + div.js-field-settings.js-field-settings-stringtemplate(class="{{#if isTypeNotSelected 'stringtemplate'}}hide{{/if}}") + label + | {{_ 'custom-field-stringtemplate-format'}} + input.js-field-stringtemplate-format(type="text" value=getStringtemplateFormat) + label + | {{_ 'custom-field-stringtemplate-separator'}} + input.js-field-stringtemplate-separator(type="text" value=getStringtemplateSeparator) + a.flex.js-field-show-on-card(class="{{#if showOnCard}}is-checked{{/if}}") .materialCheckBox(class="{{#if showOnCard}}is-checked{{/if}}") @@ -48,6 +68,10 @@ template(name="createCustomFieldPopup") span {{_ 'automatically-field-on-card'}} + a.flex.js-field-always-on-card(class="{{#if alwaysOnCard}}is-checked{{/if}}") + .materialCheckBox(class="{{#if alwaysOnCard}}is-checked{{/if}}") + span {{_ 'always-field-on-card'}} + a.flex.js-field-showLabel-on-card(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}") .materialCheckBox(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}") diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js index 92e936419..2f1fa7dd6 100644 --- a/client/components/sidebar/sidebarCustomFields.js +++ b/client/components/sidebar/sidebarCustomFields.js @@ -16,17 +16,87 @@ BlazeComponent.extendComponent({ }).register('customFieldsSidebar'); const CreateCustomFieldPopup = BlazeComponent.extendComponent({ - _types: ['text', 'number', 'date', 'dropdown'], + _types: [ + 'text', + 'number', + 'date', + 'dropdown', + 'currency', + 'checkbox', + 'stringtemplate', + ], + + _currencyList: [ + { + name: 'US Dollar', + code: 'USD', + }, + { + name: 'Euro', + code: 'EUR', + }, + { + name: 'Yen', + code: 'JPY', + }, + { + name: 'Pound Sterling', + code: 'GBP', + }, + { + name: 'Australian Dollar', + code: 'AUD', + }, + { + name: 'Canadian Dollar', + code: 'CAD', + }, + { + name: 'Swiss Franc', + code: 'CHF', + }, + { + name: 'Yuan Renminbi', + code: 'CNY', + }, + { + name: 'Hong Kong Dollar', + code: 'HKD', + }, + { + name: 'New Zealand Dollar', + code: 'NZD', + }, + ], onCreated() { this.type = new ReactiveVar( this.data().type ? this.data().type : this._types[0], ); + + this.currencyCode = new ReactiveVar( + this.data().settings && this.data().settings.currencyCode + ? this.data().settings.currencyCode + : this._currencyList[0].code, + ); + this.dropdownItems = new ReactiveVar( this.data().settings && this.data().settings.dropdownItems ? this.data().settings.dropdownItems : [], ); + + this.stringtemplateFormat = new ReactiveVar( + this.data().settings && this.data().settings.stringtemplateFormat + ? this.data().settings.stringtemplateFormat + : '', + ); + + this.stringtemplateSeparator = new ReactiveVar( + this.data().settings && this.data().settings.stringtemplateSeparator + ? this.data().settings.stringtemplateSeparator + : '', + ); }, types() { @@ -44,6 +114,18 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ return this.type.get() !== type; }, + getCurrencyCodes() { + const currentCode = this.currencyCode.get(); + + return this._currencyList.map(({ name, code }) => { + return { + name: `${code} - ${name}`, + value: code, + selected: code === currentCode, + }; + }); + }, + getDropdownItems() { const items = this.dropdownItems.get(); Array.from(this.findAll('.js-field-settings-dropdown input')).forEach( @@ -59,9 +141,22 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ return items; }, + getStringtemplateFormat() { + return this.stringtemplateFormat.get(); + }, + + getStringtemplateSeparator() { + return this.stringtemplateSeparator.get(); + }, + getSettings() { const settings = {}; switch (this.type.get()) { + case 'currency': { + const currencyCode = this.currencyCode.get(); + settings.currencyCode = currencyCode; + break; + } case 'dropdown': { const dropdownItems = this.getDropdownItems().filter( item => !!item.name.trim(), @@ -69,6 +164,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ settings.dropdownItems = dropdownItems; break; } + case 'stringtemplate': { + const stringtemplateFormat = this.stringtemplateFormat.get(); + settings.stringtemplateFormat = stringtemplateFormat; + + const stringtemplateSeparator = this.stringtemplateSeparator.get(); + settings.stringtemplateSeparator = stringtemplateSeparator; + break; + } } return settings; }, @@ -80,6 +183,10 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ const value = evt.target.value; this.type.set(value); }, + 'change .js-field-currency'(evt) { + const value = evt.target.value; + this.currencyCode.set(value); + }, 'keydown .js-dropdown-item.last'(evt) { if (evt.target.value.trim() && evt.keyCode === 13) { const items = this.getDropdownItems(); @@ -87,6 +194,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ evt.target.value = ''; } }, + 'input .js-field-stringtemplate-format'(evt) { + const value = evt.target.value; + this.stringtemplateFormat.set(value); + }, + 'input .js-field-stringtemplate-separator'(evt) { + const value = evt.target.value; + this.stringtemplateSeparator.set(value); + }, 'click .js-field-show-on-card'(evt) { let $target = $(evt.target); if (!$target.hasClass('js-field-show-on-card')) { @@ -103,6 +218,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ $target.find('.materialCheckBox').toggleClass('is-checked'); $target.toggleClass('is-checked'); }, + 'click .js-field-always-on-card'(evt) { + let $target = $(evt.target); + if (!$target.hasClass('js-field-always-on-card')) { + $target = $target.parent(); + } + $target.find('.materialCheckBox').toggleClass('is-checked'); + $target.toggleClass('is-checked'); + }, 'click .js-field-showLabel-on-card'(evt) { let $target = $(evt.target); if (!$target.hasClass('js-field-showLabel-on-card')) { @@ -123,6 +246,8 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({ this.find('.js-field-showLabel-on-card.is-checked') !== null, automaticallyOnCard: this.find('.js-field-automatically-on-card.is-checked') !== null, + alwaysOnCard: + this.find('.js-field-always-on-card.is-checked') !== null, }; // insert or update diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade index 7f31dada0..641193d6e 100644 --- a/client/components/sidebar/sidebarFilters.jade +++ b/client/components/sidebar/sidebarFilters.jade @@ -4,10 +4,14 @@ and #each x in y constructors to fix this. template(name="filterSidebar") + h3 {{_ 'list-filter-label'}} ul.sidebar-list - span {{_ 'list-filter-label'}} form.js-list-filter input(type="text") + hr + h3 + i.fa.fa-tags + | {{_ 'filter-labels-label'}} ul.sidebar-list li(class="{{#if Filter.labelIds.isSelected undefined}}active{{/if}}") a.name.js-toggle-label-filter @@ -27,6 +31,9 @@ template(name="filterSidebar") if Filter.labelIds.isSelected _id i.fa.fa-check hr + h3 + i.fa.fa-users + | {{_ 'filter-member-label'}} ul.sidebar-list li(class="{{#if Filter.members.isSelected undefined}}active{{/if}}") a.name.js-toggle-member-filter @@ -45,6 +52,67 @@ template(name="filterSidebar") if Filter.members.isSelected _id i.fa.fa-check hr + h3 + i.fa.fa-user + | {{_ 'filter-assignee-label'}} + ul.sidebar-list + li(class="{{#if Filter.assignees.isSelected undefined}}active{{/if}}") + a.name.js-toggle-assignee-filter + span.sidebar-list-item-description + | {{_ 'filter-no-assignee'}} + if Filter.assignees.isSelected undefined + i.fa.fa-check + each currentBoard.activeMembers + with getUser userId + li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}") + a.name.js-toggle-assignee-filter + +userAvatar(userId=this._id) + span.sidebar-list-item-description + = profile.fullname + | ({{ username }}) + if Filter.assignees.isSelected _id + i.fa.fa-check + + hr + h3 + i.fa.fa-list-alt + | {{_ 'filter-dates-label' }} + ul.sidebar-list + li(class="{{#if Filter.dueAt.isSelected 'noDate'}}active{{/if}}") + a.name.js-toggle-no-due-date-filter + span.sidebar-list-item-description + | {{_ 'filter-no-due-date' }} + if Filter.dueAt.isSelected 'noDate' + i.fa.fa-check + li(class="{{#if Filter.dueAt.isSelected 'past'}}active{{/if}}") + a.name.js-toggle-overdue-filter + span.sidebar-list-item-description + | {{_ 'filter-overdue' }} + if Filter.dueAt.isSelected 'past' + i.fa.fa-check + li(class="{{#if Filter.dueAt.isSelected 'today'}}active{{/if}}") + a.name.js-toggle-due-today-filter + span.sidebar-list-item-description + | {{_ 'filter-due-today' }} + if Filter.dueAt.isSelected 'today' + i.fa.fa-check + li(class="{{#if Filter.dueAt.isSelected 'tomorrow'}}active{{/if}}") + a.name.js-toggle-due-tomorrow-filter + span.sidebar-list-item-description + | {{_ 'filter-due-tomorrow' }} + if Filter.dueAt.isSelected 'tomorrow' + i.fa.fa-check + li(class="{{#if Filter.dueAt.isSelected 'week'}}active{{/if}}") + a.name.js-toggle-due-this-week-filter + span.sidebar-list-item-description + | {{_ 'filter-due-this-week' }} + if Filter.dueAt.isSelected 'week' + i.fa.fa-check + + hr + h3 + i.fa.fa-list-alt + | {{_ 'filter-custom-fields-label'}} ul.sidebar-list li(class="{{#if Filter.customFields.isSelected undefined}}active{{/if}}") a.name.js-toggle-custom-fields-filter @@ -60,6 +128,8 @@ template(name="filterSidebar") if Filter.customFields.isSelected _id i.fa.fa-check hr + h3 + | {{_ 'other-filters-label'}} ul.sidebar-list li(class="{{#if Filter.archive.isSelected _id}}active{{/if}}") a.name.js-toggle-archive-filter @@ -67,7 +137,6 @@ template(name="filterSidebar") | {{_ 'filter-show-archive'}} if Filter.archive.isSelected _id i.fa.fa-check - hr ul.sidebar-list li(class="{{#if Filter.hideEmpty.isSelected _id}}active{{/if}}") a.name.js-toggle-hideEmpty-filter @@ -76,7 +145,7 @@ template(name="filterSidebar") if Filter.hideEmpty.isSelected _id i.fa.fa-check hr - span {{_ 'advanced-filter-label'}} + h3 {{_ 'advanced-filter-label'}} input.js-field-advanced-filter(type="text") span {{_ 'advanced-filter-description'}} if Filter.isActive @@ -89,6 +158,9 @@ template(name="filterSidebar") span {{_ 'filter-to-selection'}} template(name="multiselectionSidebar") + h3 + i.fa.fa-tags + | {{_ 'multi-selection-label'}} ul.sidebar-list each currentBoard.labels li @@ -104,6 +176,9 @@ template(name="multiselectionSidebar") else if someSelectedElementHave 'label' _id i.fa.fa-ellipsis-h hr + h3 + i.fa.fa-users + | {{_ 'multi-selection-member'}} ul.sidebar-list each currentBoard.activeMembers with getUser userId @@ -117,7 +192,7 @@ template(name="multiselectionSidebar") i.fa.fa-check else if someSelectedElementHave 'member' _id i.fa.fa-ellipsis-h - unless currentUser.isWorker + if currentUser.isBoardAdmin hr a.sidebar-btn.js-move-selection i.fa.fa-share diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js index ee0176b97..7f527b9f5 100644 --- a/client/components/sidebar/sidebarFilters.js +++ b/client/components/sidebar/sidebarFilters.js @@ -18,6 +18,36 @@ BlazeComponent.extendComponent({ Filter.members.toggle(this.currentData()._id); Filter.resetExceptions(); }, + 'click .js-toggle-assignee-filter'(evt) { + evt.preventDefault(); + Filter.assignees.toggle(this.currentData()._id); + Filter.resetExceptions(); + }, + 'click .js-toggle-no-due-date-filter'(evt) { + evt.preventDefault(); + Filter.dueAt.noDate(); + Filter.resetExceptions(); + }, + 'click .js-toggle-overdue-filter'(evt) { + evt.preventDefault(); + Filter.dueAt.past(); + Filter.resetExceptions(); + }, + 'click .js-toggle-due-today-filter'(evt) { + evt.preventDefault(); + Filter.dueAt.today(); + Filter.resetExceptions(); + }, + 'click .js-toggle-due-tomorrow-filter'(evt) { + evt.preventDefault(); + Filter.dueAt.tomorrow(); + Filter.resetExceptions(); + }, + 'click .js-toggle-due-this-week-filter'(evt) { + evt.preventDefault(); + Filter.dueAt.thisWeek(); + Filter.resetExceptions(); + }, 'click .js-toggle-archive-filter'(evt) { evt.preventDefault(); Filter.archive.toggle(this.currentData()._id); @@ -129,6 +159,15 @@ BlazeComponent.extendComponent({ }, }).register('multiselectionSidebar'); +Template.multiselectionSidebar.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, + isCommentOnly() { + return Meteor.user().isCommentOnly(); + }, +}); + Template.disambiguateMultiLabelPopup.events({ 'click .js-remove-label'() { mutateSelectedCards('removeLabel', this._id); @@ -153,7 +192,8 @@ Template.disambiguateMultiMemberPopup.events({ Template.moveSelectionPopup.events({ 'click .js-select-list'() { - mutateSelectedCards('move', this._id); + // Move the minicard to the end of the target list + mutateSelectedCards('moveToEndOfList', { listId: this._id }); EscapeActions.executeUpTo('multiselection'); }, }); diff --git a/client/components/sidebar/sidebarSearches.jade b/client/components/sidebar/sidebarSearches.jade index 4ee7fc9c0..212ba57a2 100644 --- a/client/components/sidebar/sidebarSearches.jade +++ b/client/components/sidebar/sidebarSearches.jade @@ -1,12 +1,12 @@ template(name="searchSidebar") form.js-search-term-form input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto") - .list-body.js-perfect-scrollbar + .list-body .minilists.clearfix.js-minilists each (lists) - a.minilist-wrapper.js-minilist(href=absoluteUrl) + a.minilist-wrapper.js-minilist(href=originRelativeUrl) +minilist(this) .minicards.clearfix.js-minicards each (results) - a.minicard-wrapper.js-minicard(href=absoluteUrl) + a.minicard-wrapper.js-minicard(href=originRelativeUrl) +minicard(this) diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index 72a7f0544..5319402c2 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -1,5 +1,5 @@ template(name="swimlaneHeader") - .swimlane-header-wrap.js-swimlane-header(class='{{#if colorClass}}swimlane-{{colorClass}}{{/if}}') + .swimlane-header-wrap.js-swimlane-header(class=colorClass title="{{_ 'rename'}}") if this.isTemplateContainer +swimlaneFixedHeader(this) else @@ -11,11 +11,20 @@ template(name="swimlaneHeader") template(name="swimlaneFixedHeader") .swimlane-header( class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") - = title + if $eq title 'Card Templates' + | {{_ 'card-templates-swimlane'}} + else if $eq title 'List Templates' + | {{_ 'list-templates-swimlane'}} + else if $eq title 'Board Templates' + | {{_ 'board-templates-swimlane'}} + else + +viewer + = title .swimlane-header-menu unless currentUser.isCommentOnly - a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon - a.fa.fa-navicon.js-open-swimlane-menu + 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'}}") unless isMiniScreen if showDesktopDragHandles a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle @@ -37,6 +46,10 @@ template(name="swimlaneActionPopup") hr ul.pop-over-list li: a.js-close-swimlane {{_ 'archive-swimlane'}} + ul.pop-over-list + li: a.js-copy-swimlane {{_ 'copy-swimlane'}} + ul.pop-over-list + li: a.js-move-swimlane {{_ 'move-swimlane'}} template(name="swimlaneAddPopup") unless currentUser.isCommentOnly diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 3032966dd..9d01ee34c 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -1,5 +1,3 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); const { calculateIndexData } = Utils; let swimlaneColors; @@ -35,7 +33,7 @@ Template.swimlaneHeader.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; @@ -43,6 +41,12 @@ Template.swimlaneHeader.helpers({ }, }); +Template.swimlaneFixedHeader.helpers({ + isBoardAdmin() { + return Meteor.user().isBoardAdmin(); + }, +}); + Template.swimlaneActionPopup.events({ 'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'), 'click .js-close-swimlane'(event) { @@ -50,6 +54,14 @@ Template.swimlaneActionPopup.events({ this.archive(); Popup.close(); }, + 'click .js-move-swimlane': Popup.open('moveSwimlane'), + 'click .js-copy-swimlane': Popup.open('copySwimlane'), +}); + +Template.swimlaneActionPopup.events({ + isCommentOnly() { + return Meteor.user().isCommentOnly(); + }, }); BlazeComponent.extendComponent({ diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade index 9b00d9e80..3040917bb 100644 --- a/client/components/swimlanes/swimlanes.jade +++ b/client/components/swimlanes/swimlanes.jade @@ -2,7 +2,7 @@ template(name="swimlane") .swimlane +swimlaneHeader unless collapseSwimlane - .swimlane.js-lists.js-swimlane + .swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}") if isMiniScreen if currentListIsInThisSwimlane _id +list(currentList) @@ -45,18 +45,38 @@ template(name="listsGroup") template(name="addListForm") unless currentUser.isWorker .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}") - .list-header-add - +inlinedForm(autoclose=false) - input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}" - autocomplete="off" autofocus) - .edit-controls.clearfix - button.primary.confirm(type="submit") {{_ 'save'}} - unless currentBoard.isTemplatesBoard - unless currentBoard.isTemplateBoard - span.quiet - | {{_ 'or'}} - a.js-list-template {{_ 'template'}} - else - a.open-list-composer.js-open-inlined-form - i.fa.fa-plus - | {{_ 'add-list'}} + if currentUser.isBoardAdmin + .list-header-add + +inlinedForm(autoclose=false) + input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}" + autocomplete="off" autofocus) + .edit-controls.clearfix + button.primary.confirm(type="submit") {{_ 'save'}} + unless currentBoard.isTemplatesBoard + unless currentBoard.isTemplateBoard + span.quiet + | {{_ 'or'}} + a.js-list-template {{_ 'template'}} + else + a.open-list-composer.js-open-inlined-form(title="{{_ 'add-list'}}") + i.fa.fa-plus + +template(name="moveSwimlanePopup") + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each toBoard in toBoards + option(value="{{toBoard._id}}") {{toBoard.title}} + + .edit-controls.clearfix + button.primary.confirm.js-done {{_ 'done'}} + +template(name="copySwimlanePopup") + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each toBoard in toBoards + option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}} + + .edit-controls.clearfix + button.primary.confirm.js-done {{_ 'done'}} diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js index b7a55ce6e..6d72fbfef 100644 --- a/client/components/swimlanes/swimlanes.js +++ b/client/components/swimlanes/swimlanes.js @@ -1,6 +1,4 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); -const { calculateIndex, enableClickOnTouch } = Utils; +const { calculateIndex } = Utils; function currentListIsInThisSwimlane(swimlaneId) { const currentList = Lists.findOne(Session.get('currentList')); @@ -23,8 +21,8 @@ function currentCardIsInThisList(listId, swimlaneId) { currentCard.listId === listId && currentCard.swimlaneId === swimlaneId ); - // Default view: board-view-lists else return currentCard && currentCard.listId === listId; + // https://github.com/wekan/wekan/issues/1623 // https://github.com/ChronikEwok/wekan/commit/cad9b20451bb6149bfb527a99b5001873b06c3de // TODO: In public board, if you would like to switch between List/Swimlane view, you could @@ -87,17 +85,14 @@ function initSortable(boardComponent, $listsDom) { }, }); - // ugly touch event hotfix - enableClickOnTouch('.js-list:not(.js-list-composer)'); - - function userIsMember() { - return ( - Meteor.user() && - Meteor.user().isBoardMember() && - !Meteor.user().isCommentOnly() && - !Meteor.user().isWorker() - ); - } + //function userIsMember() { + // return ( + // Meteor.user() && + // Meteor.user().isBoardMember() && + // !Meteor.user().isCommentOnly() && + // !Meteor.user().isWorker() + // ); + //} boardComponent.autorun(() => { let showDesktopDragHandles = false; @@ -105,13 +100,13 @@ function initSortable(boardComponent, $listsDom) { if (currentUser) { showDesktopDragHandles = (currentUser.profile || {}) .showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { showDesktopDragHandles = true; } else { showDesktopDragHandles = false; } - if (!Utils.isMiniScreen() && showDesktopDragHandles) { + if (Utils.isMiniScreen() || showDesktopDragHandles) { $listsDom.sortable({ handle: '.js-list-handle', }); @@ -122,34 +117,13 @@ function initSortable(boardComponent, $listsDom) { } const $listDom = $listsDom; - if ($listDom.data('sortable')) { + if ($listDom.data('uiSortable') || $listDom.data('sortable')) { $listsDom.sortable( 'option', 'disabled', - // Disable drag-dropping when user is not member/is worker/is miniscreen - !userIsMember(), - // Not disable drag-dropping while in multi-selection mode - // MultiSelection.isActive() || !userIsMember(), - ); - } - - if ($listDom.data('sortable')) { - $listsDom.sortable( - 'option', - 'disabled', - // Disable drag-dropping when user is not member/is worker/is miniscreen - Meteor.user().isWorker(), - // Not disable drag-dropping while in multi-selection mode - // MultiSelection.isActive() || !userIsMember(), - ); - } - - if ($listDom.data('sortable')) { - $listsDom.sortable( - 'option', - 'disabled', - // Disable drag-dropping when user is not member/is worker/is miniscreen - Utils.isMiniScreen(), + // Disable drag-dropping when user is not member/is worker + //!userIsMember() || Meteor.user().isWorker(), + !Meteor.user().isBoardAdmin(), // Not disable drag-dropping while in multi-selection mode // MultiSelection.isActive() || !userIsMember(), ); @@ -203,15 +177,14 @@ BlazeComponent.extendComponent({ if (currentUser) { showDesktopDragHandles = (currentUser.profile || {}) .showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { showDesktopDragHandles = true; } else { showDesktopDragHandles = false; } const noDragInside = ['a', 'input', 'textarea', 'p'].concat( - Utils.isMiniScreen() || - (!Utils.isMiniScreen() && showDesktopDragHandles) + Utils.isMiniScreen() || showDesktopDragHandles ? ['.js-list-handle', '.js-swimlane-header-handle'] : ['.js-list-header'], ); @@ -295,19 +268,20 @@ Template.swimlane.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; } }, canSeeAddList() { - return ( + return Meteor.user().isBoardAdmin(); + /* Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly() && !Meteor.user().isWorker() - ); + */ }, }); @@ -349,3 +323,55 @@ BlazeComponent.extendComponent({ initSortable(boardComponent, $listsDom); }, }).register('listsGroup'); + +class MoveSwimlaneComponent extends BlazeComponent { + serverMethod = 'moveSwimlane'; + + onCreated() { + this.currentSwimlane = this.currentData(); + } + + board() { + return Boards.findOne(Session.get('currentBoard')); + } + + toBoardsSelector() { + return { + archived: false, + 'members.userId': Meteor.userId(), + type: 'board', + _id: { $ne: this.board()._id }, + }; + } + + toBoards() { + return Boards.find(this.toBoardsSelector(), { sort: { title: 1 } }); + } + + events() { + return [ + { + 'click .js-done'() { + // const swimlane = Swimlanes.findOne(this.currentSwimlane._id); + const bSelect = $('.js-select-boards')[0]; + let boardId; + if (bSelect) { + boardId = bSelect.options[bSelect.selectedIndex].value; + Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId); + } + Popup.close(); + }, + }, + ]; + } +} +MoveSwimlaneComponent.register('moveSwimlanePopup'); + +(class extends MoveSwimlaneComponent { + serverMethod = 'copySwimlane'; + toBoardsSelector() { + const selector = super.toBoardsSelector(); + delete selector._id; + return selector; + } +}.register('copySwimlanePopup')); diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl index ca5611cca..48fc57940 100644 --- a/client/components/swimlanes/swimlanes.styl +++ b/client/components/swimlanes/swimlanes.styl @@ -36,6 +36,10 @@ // Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp */ +[class=swimlane] + position: sticky + left: 0 + .swimlane // Even if this background color is the same as the body we can't leave it // transparent, because that won't work during a swimlane drag. @@ -81,10 +85,16 @@ .swimlane-header-menu position: absolute padding: 5px 5px + font-size: 22px .swimlane-header-plus-icon margin-left: 5px - margin-right: 10px + padding-right: 20px + font-size: 22px + + .swimlane-header-menu-icon + padding-right: 20px + font-size: 22px .swimlane-header-handle position: absolute diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade index 7f2067ce8..33ce76f8b 100644 --- a/client/components/users/userAvatar.jade +++ b/client/components/users/userAvatar.jade @@ -1,5 +1,5 @@ template(name="userAvatar") - a.member.js-member(title="{{userData.profile.fullname}} ({{userData.username}})") + a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}") if userData.profile.avatarUrl img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}") else @@ -19,6 +19,44 @@ template(name="userAvatarInitials") svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15") text(x="50%" y="13" text-anchor="middle")= initials +template(name="orgAvatar") + a.member.orgOrTeamMember(class="js-member" title="{{orgData.orgDisplayName}}") + +boardOrgName(orgId=orgData._id) + +template(name="boardOrgRow") + tr + if orgData.orgIsActive + td {{ orgData.orgDisplayName }} + else + td {{ orgData.orgDisplayName }} + td + if currentUser.isBoardAdmin + a.member.orgOrTeamMember.add-member.js-manage-board-removeOrg(title="{{_ 'remove-from-board'}}") + i.fa.fa-minus + +template(name="boardTeamRow") + tr + if teamData.teamIsActive + td {{ teamData.teamDisplayName }} + else + td {{ teamData.teamDisplayName }} + td + if currentUser.isBoardAdmin + a.member.orgOrTeamMember.add-member.js-manage-board-removeTeam(title="{{_ 'remove-from-board'}}") + i.fa.fa-minus + +template(name="boardOrgName") + svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15") + text(x="50%" y="13" text-anchor="middle")= orgName + +template(name="teamAvatar") + a.member.orgOrTeamMember(class="js-member" title="{{teamData.teamDisplayName}}") + +boardTeamName(orgId=orgData._id) + +template(name="boardTeamName") + svg.avatar.avatar-initials(viewBox="0 0 {{teamViewPortWidth}} 15") + text(x="50%" y="13" text-anchor="middle")= teamName + template(name="userPopup") .board-member-menu .mini-profile-info @@ -72,9 +110,10 @@ template(name="cardMemberPopup") h3= user.profile.fullname p.quiet @{{ user.username }} ul.pop-over-list - if currentUser.isNotCommentOnly - if currentUser.isNotWorker - li: a.js-remove-member {{_ 'remove-member-from-card'}} + unless noRemove + if currentUser.isNotCommentOnly + if currentUser.isNotWorker + li: a.js-remove-member {{_ 'remove-member-from-card'}} if $eq currentUser._id user._id with currentUser diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js index 262a63aff..3fe008ca3 100644 --- a/client/components/users/userAvatar.js +++ b/client/components/users/userAvatar.js @@ -1,3 +1,9 @@ +import Cards from '/models/cards'; +import Avatars from '/models/avatars'; +import Users from '/models/users'; +import Org from '/models/org'; +import Team from '/models/team'; + Template.userAvatar.helpers({ userData() { // We need to handle a special case for the search results provided by the @@ -30,10 +36,6 @@ Template.userAvatar.helpers({ }, }); -Template.userAvatar.events({ - 'click .js-change-avatar': Popup.open('changeAvatar'), -}); - Template.userAvatarInitials.helpers({ initials() { const user = Users.findOne(this.userId); @@ -46,6 +48,132 @@ Template.userAvatarInitials.helpers({ }, }); +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'click .js-manage-board-removeOrg': Popup.open('removeBoardOrg'), + }, + ]; + }, +}).register('boardOrgRow'); + +Template.boardOrgRow.helpers({ + orgData() { + const orgCollection = this.esSearch ? ESSearchResults : Org; + return orgCollection.findOne(this.orgId); + }, + currentUser(){ + return Meteor.user(); + }, +}); + +Template.boardOrgName.helpers({ + orgName() { + const org = Org.findOne(this.orgId); + return org && org.orgDisplayName; + }, + + orgViewPortWidth() { + const org = Org.findOne(this.orgId); + return ((org && org.orgDisplayName.length) || 1) * 12; + }, +}); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.findOrgsOptions = new ReactiveVar({}); + + this.page = new ReactiveVar(1); + this.autorun(() => { + const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER; + this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {}); + }); + }, + + onRendered() { + this.setLoading(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + isLoading() { + return this.loading.get(); + }, + + events() { + return [ + { + 'keyup input'() { + this.setError(''); + }, + 'click .js-manage-board-removeTeam': Popup.open('removeBoardTeam'), + }, + ]; + }, +}).register('boardTeamRow'); + +Template.boardTeamRow.helpers({ + teamData() { + const teamCollection = this.esSearch ? ESSearchResults : Team; + return teamCollection.findOne(this.teamId); + }, + currentUser(){ + return Meteor.user(); + }, +}); + +Template.boardTeamName.helpers({ + teamName() { + const team = Team.findOne(this.teamId); + return team && team.teamDisplayName; + }, + + teamViewPortWidth() { + const team = Team.findOne(this.teamId); + return ((team && team.teamDisplayName.length) || 1) * 12; + }, +}); + BlazeComponent.extendComponent({ onCreated() { this.error = new ReactiveVar(''); diff --git a/client/components/users/userAvatar.styl b/client/components/users/userAvatar.styl index b962b01c3..8dca90535 100644 --- a/client/components/users/userAvatar.styl +++ b/client/components/users/userAvatar.styl @@ -9,7 +9,6 @@ avatar-radius = 50% float: left height: 30px width: @height - margin: 0 4px 4px 0 cursor: pointer user-select: none z-index: 1 @@ -29,6 +28,8 @@ avatar-radius = 50% position: absolute &.avatar-image + object-fit: cover; + object-position: center; height: 100% width: @height diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index 1cd9da6bc..456a450e3 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -1,6 +1,6 @@ template(name="headerUserBar") #header-user-bar - a.header-user-bar-name.js-open-header-member-menu + a.header-user-bar-name.js-open-header-member-menu(title="{{_ 'memberMenuPopup-title'}}") .header-user-bar-avatar +userAvatar(userId=currentUser._id) unless isMiniScreen @@ -13,6 +13,42 @@ template(name="headerUserBar") template(name="memberMenuPopup") ul.pop-over-list with currentUser + li + a.js-my-cards(href="{{pathFor 'my-cards'}}") + i.fa.fa-list + | {{_ 'my-cards'}} + li + a.js-due-cards(href="{{pathFor 'due-cards'}}") + i.fa.fa-calendar + | {{_ 'dueCards-title'}} + li + a.js-global-search(href="{{pathFor 'global-search'}}") + i.fa.fa-search + | {{_ 'globalSearch-title'}} + li + a(href="{{pathFor 'home'}}") + span.fa.fa-home + | {{_ 'all-boards'}} + li + a(href="{{pathFor 'public'}}") + span.fa.fa-globe + | {{_ 'public'}} + li + a.board-header-btn.js-open-archived-board + i.fa.fa-archive + span {{_ 'archives'}} + unless currentUser.isWorker + ul.pop-over-list + li + a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") + i.fa.fa-clone + | {{_ 'templates'}} + if currentUser.isAdmin + li + a.js-go-setting(href="{{pathFor 'setting'}}") + i.fa.fa-lock + | {{_ 'admin-panel'}} + hr li a.js-edit-profile i.fa.fa-user @@ -30,22 +66,10 @@ template(name="memberMenuPopup") a.js-change-password i.fa.fa-key | {{_ 'changePasswordPopup-title'}} - li - a.js-change-language - i.fa.fa-flag - | {{_ 'changeLanguagePopup-title'}} - if currentUser.isAdmin li - a.js-go-setting(href="{{pathFor 'setting'}}") - i.fa.fa-lock - | {{_ 'admin-panel'}} - unless currentUser.isWorker - hr - ul.pop-over-list - li - a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") - i.fa.fa-clone - | {{_ 'templates'}} + a.js-change-language + i.fa.fa-flag + | {{_ 'changeLanguagePopup-title'}} unless isSandstorm hr ul.pop-over-list @@ -112,11 +136,20 @@ template(name="changeSettingsPopup") i.fa.fa-check unless currentUser.isWorker li - label.bold + label.bold.clear i.fa.fa-sort-numeric-asc | {{_ 'show-cards-minimum-count'}} - input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false") - input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}") + input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="-1") + label.bold.clear + i.fa.fa-calendar + | {{_ 'start-day-of-week'}} + select#start-day-of-week.inline-input.left + each day in weekDays startDayOfWeek + if day.isSelected + option(selected="true", value="#{day.value}") #{day.name} + else + option(value="#{day.value}") #{day.name} + input.js-apply-user-settings.left(type="submit" value="{{_ 'apply'}}") template(name="userDeletePopup") unless currentUser.isWorker diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 847d30fb2..1a12199a1 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -1,6 +1,3 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); - Template.headerUserBar.events({ 'click .js-open-header-member-menu': Popup.open('memberMenu'), 'click .js-change-avatar': Popup.open('changeAvatar'), @@ -28,6 +25,15 @@ Template.memberMenuPopup.helpers({ }); Template.memberMenuPopup.events({ + 'click .js-my-cards'() { + Popup.close(); + }, + 'click .js-due-cards'() { + Popup.close(); + }, + 'click .js-open-archived-board'() { + Modal.open('archivedBoards'); + }, 'click .js-edit-profile': Popup.open('editProfile'), 'click .js-change-settings': Popup.open('changeSettings'), 'click .js-change-avatar': Popup.open('changeAvatar'), @@ -162,10 +168,56 @@ Template.changeLanguagePopup.helpers({ let name = lang.name; if (lang.name === 'br') { name = 'Brezhoneg'; + } else if (lang.name === 'ar-EG') { + // ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo) + name = 'مَصرى'; + } else if (lang.name === 'fa-IR') { + // fa-IR = Persian (Iran) + name = 'فارسی/پارسی (ایران‎)'; + } else if (lang.name === 'de-CH') { + name = 'Deutsch (Schweiz)'; + } else if (lang.name === 'fr-BE') { + name = 'Français (Belgique)'; + } else if (lang.name === 'fr-CA') { + name = 'Français (Canada)'; } else if (lang.name === 'ig') { name = 'Igbo'; + } else if (lang.name === 'lv') { + name = 'Latviešu'; + } else if (lang.name === 'latviešu valoda') { + name = 'Latviešu'; + } else if (lang.name === 'Español') { + name = 'español'; + } else if (lang.name === 'es_419') { + name = 'español de América Latina'; + } else if (lang.name === 'es-419') { + name = 'español de América Latina'; + } else if (lang.name === 'Español de América Latina') { + name = 'español de América Latina'; + } else if (lang.name === 'es-LA') { + name = 'español de América Latina'; + } else if (lang.name === 'Español de Argentina') { + name = 'español de Argentina'; + } else if (lang.name === 'Español de Chile') { + name = 'español de Chile'; + } else if (lang.name === 'Español de Colombia') { + name = 'español de Colombia'; + } else if (lang.name === 'Español de México') { + name = 'español de México'; + } else if (lang.name === 'es-PY') { + name = 'español de Paraguayo'; + } else if (lang.name === 'Español de Paraguayo') { + name = 'español de Paraguayo'; + } else if (lang.name === 'Español de Perú') { + name = 'español de Perú'; + } else if (lang.name === 'Español de Puerto Rico') { + name = 'español de Puerto Rico'; } else if (lang.name === 'oc') { name = 'Occitan'; + } else if (lang.name === 'st') { + name = 'Sãotomense'; + } else if (lang.name === '繁体中文(台湾)') { + name = '繁體中文(台灣)'; } return { tag, name }; }).sort(function(a, b) { @@ -189,6 +241,7 @@ Template.changeLanguagePopup.events({ 'profile.language': this.tag, }, }); + TAPi18n.setLanguage(this.tag); event.preventDefault(); }, }); @@ -198,7 +251,7 @@ Template.changeSettingsPopup.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).showDesktopDragHandles; - } else if (cookies.has('showDesktopDragHandles')) { + } else if (window.localStorage.getItem('showDesktopDragHandles')) { return true; } else { return false; @@ -208,7 +261,7 @@ Template.changeSettingsPopup.helpers({ currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).hasHiddenSystemMessages; - } else if (cookies.has('hasHiddenSystemMessages')) { + } else if (window.localStorage.getItem('hasHiddenSystemMessages')) { return true; } else { return false; @@ -219,46 +272,88 @@ Template.changeSettingsPopup.helpers({ if (currentUser) { return Meteor.user().getLimitToShowCardsCount(); } else { - return cookies.get('limitToShowCardsCount'); + return window.localStorage.getItem('limitToShowCardsCount'); + } + }, + weekDays(startDay) { + return [ + TAPi18n.__('sunday'), + TAPi18n.__('monday'), + TAPi18n.__('tuesday'), + TAPi18n.__('wednesday'), + TAPi18n.__('thursday'), + TAPi18n.__('friday'), + TAPi18n.__('saturday'), + ].map(function(day, index) { + return { name: day, value: index, isSelected: index === startDay }; + }); + }, + startDayOfWeek() { + currentUser = Meteor.user(); + if (currentUser) { + return currentUser.getStartDayOfWeek(); + } else { + return window.localStorage.getItem('startDayOfWeek'); } }, }); Template.changeSettingsPopup.events({ + 'keypress/paste #show-cards-count-at'() { + let keyCode = event.keyCode; + let charCode = String.fromCharCode(keyCode); + let regex = new RegExp('[-0-9]'); + let ret = regex.test(charCode); + return ret; + }, 'click .js-toggle-desktop-drag-handles'() { currentUser = Meteor.user(); if (currentUser) { Meteor.call('toggleDesktopDragHandles'); - } else if (cookies.has('showDesktopDragHandles')) { - cookies.remove('showDesktopDragHandles'); + } else if (window.localStorage.getItem('showDesktopDragHandles')) { + window.localStorage.removeItem('showDesktopDragHandles'); } else { - cookies.set('showDesktopDragHandles', 'true'); + window.localStorage.setItem('showDesktopDragHandles', 'true'); } }, 'click .js-toggle-system-messages'() { currentUser = Meteor.user(); if (currentUser) { Meteor.call('toggleSystemMessages'); - } else if (cookies.has('hasHiddenSystemMessages')) { - cookies.remove('hasHiddenSystemMessages'); + } else if (window.localStorage.getItem('hasHiddenSystemMessages')) { + window.localStorage.removeItem('hasHiddenSystemMessages'); } else { - cookies.set('hasHiddenSystemMessages', 'true'); + window.localStorage.setItem('hasHiddenSystemMessages', 'true'); } }, - 'click .js-apply-show-cards-at'(event, templateInstance) { + 'click .js-apply-user-settings'(event, templateInstance) { event.preventDefault(); - const minLimit = parseInt( + let minLimit = parseInt( templateInstance.$('#show-cards-count-at').val(), 10, ); + const startDay = parseInt( + templateInstance.$('#start-day-of-week').val(), + 10, + ); + const currentUser = Meteor.user(); + if (isNaN(minLimit) || minLimit < -1) { + minLimit = -1; + } if (!isNaN(minLimit)) { - currentUser = Meteor.user(); if (currentUser) { Meteor.call('changeLimitToShowCardsCount', minLimit); } else { - cookies.set('limitToShowCardsCount', minLimit); + window.localStorage.setItem('limitToShowCardsCount', minLimit); } - Popup.back(); } + if (!isNaN(startDay)) { + if (currentUser) { + Meteor.call('changeStartDayOfWeek', startDay); + } else { + window.localStorage.setItem('startDayOfWeek', startDay); + } + } + Popup.back(); }, }); diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js index 3a857314d..a4c610612 100644 --- a/client/config/blazeHelpers.js +++ b/client/config/blazeHelpers.js @@ -30,3 +30,11 @@ Blaze.registerHelper('getUser', userId => Users.findOne(userId)); Blaze.registerHelper('concat', (...args) => args.slice(0, -1).join('')); Blaze.registerHelper('isMiniScreen', () => Utils.isMiniScreen()); + +Blaze.registerHelper('isShowDesktopDragHandles', () => + Utils.isShowDesktopDragHandles(), +); + +Blaze.registerHelper('isMiniScreenOrShowDesktopDragHandles', () => + Utils.isMiniScreenOrShowDesktopDragHandles(), +); diff --git a/client/lib/cardSearch.js b/client/lib/cardSearch.js new file mode 100644 index 000000000..eb7eed64e --- /dev/null +++ b/client/lib/cardSearch.js @@ -0,0 +1,189 @@ +import Cards from '../../models/cards'; +import SessionData from '../../models/usersessiondata'; + +export class CardSearchPagedComponent extends BlazeComponent { + onCreated() { + this.searching = new ReactiveVar(false); + this.hasResults = new ReactiveVar(false); + this.hasQueryErrors = new ReactiveVar(false); + this.query = new ReactiveVar(''); + this.resultsHeading = new ReactiveVar(''); + this.searchLink = new ReactiveVar(null); + this.results = new ReactiveVar([]); + this.hasNextPage = new ReactiveVar(false); + this.hasPreviousPage = new ReactiveVar(false); + this.resultsCount = 0; + this.totalHits = 0; + this.queryErrors = null; + this.resultsPerPage = 25; + this.sessionId = SessionData.getSessionId(); + this.subscriptionHandle = null; + this.serverError = new ReactiveVar(false); + + const that = this; + this.subscriptionCallbacks = { + onReady() { + that.getResults(); + that.searching.set(false); + that.hasResults.set(true); + that.serverError.set(false); + }, + onError(error) { + that.searching.set(false); + that.hasResults.set(false); + that.serverError.set(true); + // eslint-disable-next-line no-console + console.log('Error.reason:', error.reason); + // eslint-disable-next-line no-console + console.log('Error.message:', error.message); + // eslint-disable-next-line no-console + console.log('Error.stack:', error.stack); + }, + }; + } + + resetSearch() { + this.searching.set(false); + this.results.set([]); + this.hasResults.set(false); + this.hasQueryErrors.set(false); + this.resultsHeading.set(''); + this.serverError.set(false); + this.resultsCount = 0; + this.totalHits = 0; + this.queryErrors = null; + } + + getSessionData(sessionId) { + return SessionData.findOne({ + sessionId: sessionId ? sessionId : SessionData.getSessionId(), + }); + } + + getResults() { + // eslint-disable-next-line no-console + // console.log('getting results'); + const sessionData = this.getSessionData(); + // eslint-disable-next-line no-console + console.log('session data:', sessionData); + const cards = []; + sessionData.cards.forEach(cardId => { + cards.push(Cards.findOne({ _id: cardId })); + }); + this.queryErrors = sessionData.errors; + if (this.queryErrors.length) { + // console.log('queryErrors:', this.queryErrorMessages()); + this.hasQueryErrors.set(true); + // return null; + } + + if (cards) { + this.totalHits = sessionData.totalHits; + this.resultsCount = cards.length; + this.resultsStart = sessionData.lastHit - this.resultsCount + 1; + this.resultsEnd = sessionData.lastHit; + this.resultsHeading.set(this.getResultsHeading()); + this.results.set(cards); + this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits); + this.hasPreviousPage.set( + sessionData.lastHit - sessionData.resultsCount > 0, + ); + return cards; + } + + this.resultsCount = 0; + return null; + } + + stopSubscription() { + if (this.subscriptionHandle) { + this.subscriptionHandle.stop(); + } + } + + getSubscription(queryParams) { + return Meteor.subscribe( + 'globalSearch', + this.sessionId, + queryParams.params, + queryParams.text, + this.subscriptionCallbacks, + ); + } + + runGlobalSearch(queryParams) { + this.searching.set(true); + this.stopSubscription(); + this.subscriptionHandle = this.getSubscription(queryParams); + } + + queryErrorMessages() { + const messages = []; + + this.queryErrors.forEach(err => { + let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value; + if (!value) { + value = err.value; + } + messages.push(TAPi18n.__(err.tag, value)); + }); + + return messages; + } + + nextPage() { + this.searching.set(true); + this.stopSubscription(); + this.subscriptionHandle = Meteor.subscribe( + 'nextPage', + this.sessionId, + this.subscriptionCallbacks, + ); + } + + previousPage() { + this.searching.set(true); + this.stopSubscription(); + this.subscriptionHandle = Meteor.subscribe( + 'previousPage', + this.sessionId, + this.subscriptionCallbacks, + ); + } + + getResultsHeading() { + if (this.resultsCount === 0) { + return TAPi18n.__('no-cards-found'); + } else if (this.resultsCount === 1) { + return TAPi18n.__('one-card-found'); + } else if (this.resultsCount === this.totalHits) { + return TAPi18n.__('n-cards-found', this.resultsCount); + } + + return TAPi18n.__('n-n-of-n-cards-found', { + start: this.resultsStart, + end: this.resultsEnd, + total: this.totalHits, + }); + } + + getSearchHref() { + const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, ''); + return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`; + } + + events() { + return [ + { + 'click .js-next-page'(evt) { + evt.preventDefault(); + this.nextPage(); + }, + 'click .js-previous-page'(evt) { + evt.preventDefault(); + this.previousPage(); + }, + }, + ]; + } +} diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js index 8ad66c5f4..b793f17ad 100644 --- a/client/lib/datepicker.js +++ b/client/lib/datepicker.js @@ -1,14 +1,31 @@ -DatePicker = BlazeComponent.extendComponent({ +// Helper function to replace HH with H for 24 hours format, because H allows also single-digit hours +function adjustedTimeFormat() { + return moment + .localeData() + .longDateFormat('LT') + .replace(/HH/i, 'H'); +} + +export class DatePicker extends BlazeComponent { template() { return 'datepicker'; - }, + } onCreated(defaultTime = '1970-01-01 08:00:00') { this.error = new ReactiveVar(''); this.card = this.data(); this.date = new ReactiveVar(moment.invalid()); this.defaultTime = defaultTime; - }, + } + + startDayOfWeek() { + const currentUser = Meteor.user(); + if (currentUser) { + return currentUser.getStartDayOfWeek(); + } else { + return 1; + } + } onRendered() { const $picker = this.$('.js-datepicker') @@ -16,6 +33,7 @@ DatePicker = BlazeComponent.extendComponent({ todayHighlight: true, todayBtn: 'linked', language: TAPi18n.getLanguage(), + weekStart: this.startDayOfWeek(), }) .on( 'changeDate', @@ -24,7 +42,7 @@ DatePicker = BlazeComponent.extendComponent({ this.error.set(''); const timeInput = this.find('#time'); timeInput.focus(); - if (!timeInput.value) { + if (!timeInput.value && this.defaultTime) { const currentHour = evt.date.getHours(); const defaultMoment = moment( currentHour > 0 ? evt.date : this.defaultTime, @@ -37,22 +55,22 @@ DatePicker = BlazeComponent.extendComponent({ if (this.date.get().isValid()) { $picker.datepicker('update', this.date.get().toDate()); } - }, + } showDate() { if (this.date.get().isValid()) return this.date.get().format('L'); return ''; - }, + } showTime() { if (this.date.get().isValid()) return this.date.get().format('LT'); return ''; - }, + } dateFormat() { return moment.localeData().longDateFormat('L'); - }, + } timeFormat() { return moment.localeData().longDateFormat('LT'); - }, + } events() { return [ @@ -67,7 +85,11 @@ DatePicker = BlazeComponent.extendComponent({ }, 'keyup .js-time-field'() { // parse for localized time format in strict mode - const dateMoment = moment(this.find('#time').value, 'LT', true); + const dateMoment = moment( + this.find('#time').value, + adjustedTimeFormat(), + true, + ); if (dateMoment.isValid()) { this.error.set(''); } @@ -79,16 +101,28 @@ DatePicker = BlazeComponent.extendComponent({ const time = evt.target.time.value || moment(new Date().setHours(12, 0, 0)).format('LT'); - + const newTime = moment(time, adjustedTimeFormat(), true); + const newDate = moment(evt.target.date.value, 'L', true); const dateString = `${evt.target.date.value} ${time}`; - const newDate = moment(dateString, 'L LT', true); - if (newDate.isValid()) { - this._storeDate(newDate.toDate()); - Popup.close(); - } else { + const newCompleteDate = moment( + dateString, + `L ${adjustedTimeFormat()}`, + true, + ); + if (!newTime.isValid()) { + this.error.set('invalid-time'); + evt.target.time.focus(); + } + if (!newDate.isValid()) { this.error.set('invalid-date'); evt.target.date.focus(); } + if (newCompleteDate.isValid()) { + this._storeDate(newCompleteDate.toDate()); + Popup.close(); + } else if (!this.error) { + this.error.set('invalid'); + } }, 'click .js-delete-date'(evt) { evt.preventDefault(); @@ -97,5 +131,5 @@ DatePicker = BlazeComponent.extendComponent({ }, }, ]; - }, -}); + } +} diff --git a/client/lib/dropImage.js b/client/lib/dropImage.js index f4e1d8ab0..559754f44 100644 --- a/client/lib/dropImage.js +++ b/client/lib/dropImage.js @@ -39,8 +39,8 @@ }; return this.each(function() { const element = this; - $(element).bind('dragenter dragover dragleave', stopFn); - return $(element).bind('drop', function(event) { + $(element).on('dragenter dragover dragleave', stopFn); + return $(element).on('drop', function(event) { stopFn(event); const files = event.dataTransfer.files; for (let i = 0; i < files.length; i++) { diff --git a/client/lib/exportHTML.js b/client/lib/exportHTML.js new file mode 100644 index 000000000..59a0612fc --- /dev/null +++ b/client/lib/exportHTML.js @@ -0,0 +1,204 @@ +const JSZip = require('jszip'); + +window.ExportHtml = Popup => { + const saveAs = function(blob, filename) { + const dl = document.createElement('a'); + dl.href = window.URL.createObjectURL(blob); + dl.onclick = event => document.body.removeChild(event.target); + dl.style.display = 'none'; + dl.target = '_blank'; + dl.download = filename; + document.body.appendChild(dl); + dl.click(); + }; + + const asyncForEach = async function(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } + }; + + const getPageHtmlString = () => { + return `${window.document.querySelector('html').outerHTML}`; + }; + + const removeAnchors = htmlString => { + const replaceOpenAnchor = htmlString.replace( + new RegExp(' { + document.querySelector('.board-sidebar.sidebar').remove(); + }; + + const addJsonExportToZip = async (zip, boardSlug) => { + const downloadJSONLink = document.querySelector('.download-json-link'); + const downloadJSONURL = downloadJSONLink.href; + const response = await fetch(downloadJSONURL); + const responseBody = await response.text(); + zip.file(`data/${boardSlug}.json`, responseBody); + }; + + const closeSidebar = () => { + document.querySelector('.board-header-btn.js-toggle-sidebar').click(); + }; + + const cleanBoardHtml = () => { + Array.from(document.querySelectorAll('script')).forEach(elem => + elem.remove(), + ); + Array.from( + document.querySelectorAll('link:not([rel="stylesheet"])'), + ).forEach(elem => elem.remove()); + document.querySelector('#header-quick-access').remove(); + Array.from( + document.querySelectorAll('#header-main-bar .board-header-btns'), + ).forEach(elem => elem.remove()); + Array.from(document.querySelectorAll('.list-composer')).forEach(elem => + elem.remove(), + ); + Array.from( + document.querySelectorAll( + '.list-composer,.js-card-composer, .js-add-card', + ), + ).forEach(elem => elem.remove()); + Array.from(document.querySelectorAll('[href]:not(link)')).forEach(elem => + elem.attributes.removeNamedItem('href'), + ); + Array.from(document.querySelectorAll('[href]')).forEach(elem => { + // eslint-disable-next-line no-self-assign + elem.href = elem.href; + // eslint-disable-next-line no-self-assign + elem.src = elem.src; + }); + Array.from(document.querySelectorAll('.is-editable')).forEach(elem => { + elem.classList.remove('is-editable'); + }); + }; + + const getBoardSlug = () => { + return window.location.href.split('/').pop(); + }; + + const getStylesheetList = () => { + return Array.from( + document.querySelectorAll('link[href][rel="stylesheet"]'), + ); + }; + + const downloadStylesheets = async (stylesheets, zip) => { + await asyncForEach(stylesheets, async elem => { + const response = await fetch(elem.href); + const responseBody = await response.text(); + + const finalResponse = responseBody.replace( + new RegExp('packages/[^/]+/upstream/', 'gim'), + '../', + ); + + const filename = elem.href + .split('/') + .pop() + .split('?') + .shift(); + const fileFullPath = `style/${filename}`; + zip.file(fileFullPath, finalResponse); + elem.href = `../${fileFullPath}`; + }); + }; + + const getSrcAttached = () => { + return Array.from(document.querySelectorAll('[src]')); + }; + + const downloadSrcAttached = async (elements, zip, boardSlug) => { + await asyncForEach(elements, async elem => { + const response = await fetch(elem.src); + const responseBody = await response.blob(); + const filename = elem.src + .split('/') + .pop() + .split('?') + .shift(); + const fileFullPath = `${boardSlug}/${elem.tagName.toLowerCase()}/${filename}`; + zip.file(fileFullPath, responseBody); + elem.src = `./${elem.tagName.toLowerCase()}/${filename}`; + }); + }; + + const removeCssUrlSurround = url => { + const working = url || ''; + return working + .split('url(') + .join('') + .split('")') + .join('') + .split('"') + .join('') + .split("')") + .join('') + .split("'") + .join('') + .split(')') + .join(''); + }; + + const getCardCovers = () => { + return Array.from(document.querySelectorAll('.minicard-cover')).filter( + elem => elem.style['background-image'], + ); + }; + + const downloadCardCovers = async (elements, zip, boardSlug) => { + await asyncForEach(elements, async elem => { + const response = await fetch( + removeCssUrlSurround(elem.style['background-image']), + ); + const responseBody = await response.blob(); + const filename = removeCssUrlSurround(elem.style['background-image']) + .split('/') + .pop() + .split('?') + .shift() + .split('#') + .shift(); + const fileFullPath = `${boardSlug}/covers/${filename}`; + zip.file(fileFullPath, responseBody); + elem.style = "background-image: url('" + `covers/${filename}` + "')"; + }); + }; + + const addBoardHTMLToZip = (boardSlug, zip) => { + ensureSidebarRemoved(); + const htmlOutputPath = `${boardSlug}/index.html`; + zip.file( + htmlOutputPath, + new Blob([removeAnchors(getPageHtmlString())], { + type: 'application/html', + }), + ); + }; + + return async () => { + const zip = new JSZip(); + const boardSlug = getBoardSlug(); + + await addJsonExportToZip(zip, boardSlug); + Popup.close(); + closeSidebar(); + cleanBoardHtml(); + + await downloadStylesheets(getStylesheetList(), zip); + await downloadSrcAttached(getSrcAttached(), zip, boardSlug); + await downloadCardCovers(getCardCovers(), zip, boardSlug); + + addBoardHTMLToZip(boardSlug, zip); + + const content = await zip.generateAsync({ type: 'blob' }); + saveAs(content, `${boardSlug}.zip`); + window.location.reload(); + }; +}; diff --git a/client/lib/filter.js b/client/lib/filter.js index 592eb4ab4..94d931cee 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -7,6 +7,154 @@ function showFilterSidebar() { Sidebar.setView('filter'); } +class DateFilter { + constructor() { + this._dep = new Tracker.Dependency(); + this.subField = ''; // Prevent name mangling in Filter + this._filter = null; + this._filterState = null; + } + + _updateState(state) { + this._filterState = state; + showFilterSidebar(); + this._dep.changed(); + } + + // past builds a filter for all dates before now + past() { + if (this._filterState == 'past') { + this.reset(); + return; + } + this._filter = { $lte: moment().toDate() }; + this._updateState('past'); + } + + // today is a convenience method for calling relativeDay with 0 + today() { + if (this._filterState == 'today') { + this.reset(); + return; + } + this.relativeDay(0); + this._updateState('today'); + } + + // tomorrow is a convenience method for calling relativeDay with 1 + tomorrow() { + if (this._filterState == 'tomorrow') { + this.reset(); + return; + } + this.relativeDay(1); + this._updateState('tomorrow'); + } + + // thisWeek is a convenience method for calling relativeWeek with 1 + thisWeek() { + this.relativeWeek(1); + } + + // relativeDay builds a filter starting from now and including all + // days up to today +/- offset. + relativeDay(offset) { + if (this._filterState == 'day') { + this.reset(); + return; + } + + var startDay = moment() + .startOf('day') + .toDate(), + endDay = moment() + .endOf('day') + .add(offset, 'day') + .toDate(); + + if (offset >= 0) { + this._filter = { $gte: startDay, $lte: endDay }; + } else { + this._filter = { $lte: startDay, $gte: endDay }; + } + + this._updateState('day'); + } + + // relativeWeek builds a filter starting from today and including all + // weeks up to today +/- offset. This considers the user's preferred + // start of week day (as defined by Meteor). + relativeWeek(offset) { + if (this._filterState == 'week') { + this.reset(); + return; + } + + // getStartDayOfWeek returns the offset from Sunday of the user's + // preferred starting day of the week. This date should be added + // to the moment start of week to get the real start of week date. + // The default is 1, meaning Monday. + const currentUser = Meteor.user(); + const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1; + + // Moments are mutable so they must be cloned before modification + var thisWeekStart = moment() + .startOf('day') + .startOf('week') + .add(weekStartDay, 'days'); + var thisWeekEnd = thisWeekStart + .clone() + .add(offset, 'week') + .endOf('day'); + var startDate = thisWeekStart.toDate(); + var endDate = thisWeekEnd.toDate(); + + if (offset >= 0) { + this._filter = { $gte: startDate, $lte: endDate }; + } else { + this._filter = { $lte: startDate, $gte: endDate }; + } + + this._updateState('week'); + } + + // noDate builds a filter for items where date is not set + noDate() { + if (this._filterState == 'noDate') { + this.reset(); + return; + } + this._filter = null; + this._updateState('noDate'); + } + + reset() { + this._filter = null; + this._filterState = null; + this._dep.changed(); + } + + isSelected(val) { + this._dep.depend(); + return this._filterState == val; + } + + _isActive() { + this._dep.depend(); + return this._filterState !== null; + } + + _getMongoSelector() { + this._dep.depend(); + return this._filter; + } + + _getEmptySelector() { + this._dep.depend(); + return null; + } +} + // Use a "set" filter for a field that is a set of documents uniquely // identified. For instance `{ labels: ['labelA', 'labelC', 'labelD'] }`. // use "subField" for searching inside object Fields. @@ -459,13 +607,23 @@ Filter = { // before changing the schema. labelIds: new SetFilter(), members: new SetFilter(), + assignees: new SetFilter(), archive: new SetFilter(), hideEmpty: new SetFilter(), + dueAt: new DateFilter(), customFields: new SetFilter('_id'), advanced: new AdvancedFilter(), lists: new AdvancedFilter(), // we need the ability to filter list by name as well - _fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'], + _fields: [ + 'labelIds', + 'members', + 'assignees', + 'archive', + 'hideEmpty', + 'dueAt', + 'customFields', + ], // We don't filter cards that have been added after the last filter change. To // implement this we keep the id of these cards in this `_exceptions` fields diff --git a/client/lib/i18n.js b/client/lib/i18n.js index b4a95eae2..dbce251e9 100644 --- a/client/lib/i18n.js +++ b/client/lib/i18n.js @@ -4,24 +4,24 @@ Meteor.startup(() => { TAPi18n.conf.i18n_files_route = Meteor._relativeToSiteRootUrl('/tap-i18n'); - Tracker.autorun(() => { - const currentUser = Meteor.user(); - let language; - if (currentUser) { - language = currentUser.profile && currentUser.profile.language; - } + const currentUser = Meteor.user(); + let language; + if (currentUser) { + language = currentUser.profile && currentUser.profile.language; + } - if (!language) { - if (navigator.languages) { - language = navigator.languages[0]; - } else { - language = navigator.language || navigator.userLanguage; - } + if (!language) { + if (navigator.languages) { + language = navigator.languages[0]; + } else { + language = navigator.language || navigator.userLanguage; } + } - if (language) { - TAPi18n.setLanguage(language); - T9n.setLanguage(language); - } - }); + if (language) { + TAPi18n.setLanguage(language); + // eslint-disable-next-line no-console + // console.log('language set!'); + T9n.setLanguage(language); + } }); diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js old mode 100755 new mode 100644 index da33f8064..9f35009d3 --- a/client/lib/keyboard.js +++ b/client/lib/keyboard.js @@ -1,6 +1,16 @@ // XXX There is no reason to define these shortcuts globally, they should be // attached to a template (most of them will go in the `board` template). +function getHoveredCardId() { + const card = $('.js-minicard:hover').get(0); + if (!card) return null; + return Blaze.getData(card)._id; +} + +function getSelectedCardId() { + return Session.get('selectedCard') || getHoveredCardId(); +} + Mousetrap.bind('?', () => { FlowRouter.go('shortcuts'); }); @@ -35,6 +45,14 @@ Mousetrap.bind('f', () => { } }); +Mousetrap.bind('/', () => { + if (Sidebar.isOpen() && Sidebar.getView() === 'search') { + Sidebar.toggle(); + } else { + Sidebar.setView('search'); + } +}); + Mousetrap.bind(['down', 'up'], (evt, key) => { if (!Session.get('currentCard')) { return; @@ -50,9 +68,9 @@ Mousetrap.bind(['down', 'up'], (evt, key) => { } }); -// XXX This shortcut should also work when hovering over a card in board view Mousetrap.bind('space', evt => { - if (!Session.get('currentCard')) { + const cardId = getSelectedCardId(); + if (!cardId) { return; } @@ -62,7 +80,7 @@ Mousetrap.bind('space', evt => { } if (Meteor.user().isBoardMember()) { - const card = Cards.findOne(Session.get('currentCard')); + const card = Cards.findOne(cardId); card.toggleMember(currentUserId); // We should prevent scrolling in card when spacebar is clicked // This should do it according to Mousetrap docs, but it doesn't @@ -70,9 +88,9 @@ Mousetrap.bind('space', evt => { } }); -// XXX This shortcut should also work when hovering over a card in board view Mousetrap.bind('c', evt => { - if (!Session.get('currentCard')) { + const cardId = getSelectedCardId(); + if (!cardId) { return; } @@ -86,7 +104,7 @@ Mousetrap.bind('c', evt => { !Meteor.user().isCommentOnly() && !Meteor.user().isWorker() ) { - const card = Cards.findOne(Session.get('currentCard')); + const card = Cards.findOne(cardId); card.archive(); // We should prevent scrolling in card when spacebar is clicked // This should do it according to Mousetrap docs, but it doesn't @@ -97,19 +115,23 @@ Mousetrap.bind('c', evt => { Template.keyboardShortcuts.helpers({ mapping: [ { - keys: ['W'], + keys: ['w'], action: 'shortcut-toggle-sidebar', }, { - keys: ['Q'], + keys: ['q'], action: 'shortcut-filter-my-cards', }, { - keys: ['F'], + keys: ['f'], action: 'shortcut-toggle-filterbar', }, { - keys: ['X'], + keys: ['/'], + action: 'shortcut-toggle-searchbar', + }, + { + keys: ['x'], action: 'shortcut-clear-filters', }, { @@ -129,7 +151,7 @@ Template.keyboardShortcuts.helpers({ action: 'shortcut-assign-self', }, { - keys: ['C'], + keys: ['c'], action: 'archive-card', }, ], diff --git a/client/lib/modal.js b/client/lib/modal.js index 1086734e8..00b6fc4b7 100644 --- a/client/lib/modal.js +++ b/client/lib/modal.js @@ -50,5 +50,5 @@ EscapeActions.register( 'modalWindow', () => Modal.close(), () => Modal.isOpen(), - { noClickEscapeOn: '.modal-container' }, + { noClickEscapeOn: '.modal-container,.model-content' }, ); diff --git a/client/lib/pasteImage.js b/client/lib/pasteImage.js index e6f8c6c2d..195a781df 100644 --- a/client/lib/pasteImage.js +++ b/client/lib/pasteImage.js @@ -35,7 +35,7 @@ options = $.extend({}, defaults, options); return this.each(function() { const element = this; - return $(element).bind('paste', function(event) { + return $(element).on('paste', function(event) { const types = event.clipboardData.types; const items = event.clipboardData.items; for (let i = 0; i < types.length; i++) { diff --git a/client/lib/popup.js b/client/lib/popup.js index 8095fbd2f..cae226594 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -49,7 +49,7 @@ window.Popup = new (class { // has one. This allows us to position a sub-popup exactly at the same // position than its parent. let openerElement; - if (clickFromPopup(evt)) { + if (clickFromPopup(evt) && self._getTopStack()) { openerElement = self._getTopStack().openerElement; } else { self._stack = []; diff --git a/client/lib/spinner.js b/client/lib/spinner.js new file mode 100644 index 000000000..d7078bda4 --- /dev/null +++ b/client/lib/spinner.js @@ -0,0 +1,27 @@ +Meteor.subscribe('setting'); + +import { ALLOWED_WAIT_SPINNERS } from '/config/const'; + +export class Spinner extends BlazeComponent { + currentSettings() { + return Settings.findOne(); + } + + getSpinnerName() { + let ret = 'Bounce'; + let defaultWaitSpinner = Meteor.settings.public.WAIT_SPINNER; + if (defaultWaitSpinner && ALLOWED_WAIT_SPINNERS.includes(defaultWaitSpinner)) { + ret = defaultWaitSpinner; + } + let settings = this.currentSettings(); + + if (settings && settings.spinnerName) { + ret = settings.spinnerName; + } + return ret; + } + + getSpinnerTemplate() { + return 'spinner' + this.getSpinnerName().replace(/-/, ''); + } +} diff --git a/client/lib/tests/Utils.tests.js b/client/lib/tests/Utils.tests.js new file mode 100644 index 000000000..5a368876c --- /dev/null +++ b/client/lib/tests/Utils.tests.js @@ -0,0 +1,197 @@ +/* eslint-env mocha */ +import sinon from 'sinon'; +import { expect } from 'chai'; +import { Random } from 'meteor/random'; +import '../utils'; + + +describe('Utils', function() { + beforeEach(function() { + sinon.stub(Utils, 'reload').callsFake(() => {}); + }); + + afterEach(function() { + window.localStorage.removeItem(boardView); + sinon.restore(); + }); + + const boardView = 'boardView'; + + describe(Utils.setBoardView.name, function() { + it('sets the board view if the user exists', function(done) { + const viewId = Random.id(); + const user = { + setBoardView: (view) => { + expect(view).to.equal(viewId); + done(); + }, + }; + sinon.stub(Meteor, 'user').callsFake(() => user); + Utils.setBoardView(viewId); + + expect(window.localStorage.getItem(boardView)).to.equal(viewId); + }); + + it('sets a specific view if no user exists but a view is defined', function() { + const views = [ + 'board-view-swimlanes', + 'board-view-lists', + 'board-view-cal' + ]; + + sinon.stub(Meteor, 'user').callsFake(() => {}); + + views.forEach(viewName => { + Utils.setBoardView(viewName); + expect(window.localStorage.getItem(boardView)).to.equal(viewName); + }); + }); + + it('sets a default view if no user and no view are given', function() { + sinon.stub(Meteor, 'user').callsFake(() => {}); + Utils.setBoardView(); + expect(window.localStorage.getItem(boardView)).to.equal('board-view-swimlanes'); + }); + }); + + describe(Utils.unsetBoardView.name, function() { + it('removes the boardview from localStoage', function() { + window.localStorage.setItem(boardView, Random.id()); + window.localStorage.setItem('collapseSwimlane', Random.id()); + + Utils.unsetBoardView(); + + expect(window.localStorage.getItem(boardView)).to.equal(null); + expect(window.localStorage.getItem('collapseSwimlane')).to.equal(null); + }); + }); + + describe(Utils.boardView.name, function() { + it('returns the user\'s board view if a user exists', function() { + const viewId = Random.id(); + const user = {}; + sinon.stub(Meteor, 'user').callsFake(() => user); + expect(Utils.boardView()).to.equal(undefined); + + const boardView = Random.id(); + user.profile = { boardView }; + + expect(Utils.boardView()).to.equal(boardView); + }); + it('returns the current defined view', function() { + const views = [ + 'board-view-swimlanes', + 'board-view-lists', + 'board-view-cal' + ]; + + sinon.stub(Meteor, 'user').callsFake(() => {}); + + views.forEach(viewName => { + window.localStorage.setItem(boardView, viewName); + expect(Utils.boardView()).to.equal(viewName); + }); + }); + it('returns a default if nothing is set', function() { + sinon.stub(Meteor, 'user').callsFake(() => {}); + expect(Utils.boardView()).to.equal('board-view-swimlanes'); + expect(window.localStorage.getItem(boardView)).to.equal('board-view-swimlanes'); + }); + }); + + describe(Utils.myCardsSort.name, function() { + it('has no tests yet'); + }); + + describe(Utils.myCardsSortToggle.name, function() { + it('has no tests yet'); + }); + + describe(Utils.setMyCardsSort.name, function() { + it('has no tests yet'); + }); + + describe(Utils.archivedBoardIds.name, function() { + it('has no tests yet'); + }); + + describe(Utils.dueCardsView.name, function() { + it('has no tests yet'); + }); + + describe(Utils.setDueCardsView.name, function() { + it('has no tests yet'); + }); + + describe(Utils.goBoardId.name, function() { + it('has no tests yet'); + }); + + describe(Utils.goCardId.name, function() { + it('has no tests yet'); + }); + + describe(Utils.processUploadedAttachment.name, function() { + it('has no tests yet'); + }); + + describe(Utils.shrinkImage.name, function() { + it('has no tests yet'); + }); + + describe(Utils.capitalize.name, function() { + it('has no tests yet'); + }); + + describe(Utils.isMiniScreen.name, function() { + it('has no tests yet'); + }); + + describe(Utils.isShowDesktopDragHandles.name, function() { + it('has no tests yet'); + }); + + describe(Utils.isMiniScreenOrShowDesktopDragHandles.name, function() { + it('has no tests yet'); + }); + + describe(Utils.calculateIndexData.name, function() { + it('has no tests yet'); + }); + + describe(Utils.calculateIndex.name, function() { + it('has no tests yet'); + }); + + describe(Utils.isTouchDevice.name, function() { + it('has no tests yet'); + }); + + describe(Utils.calculateTouchDistance.name, function() { + it('has no tests yet'); + }); + + describe(Utils.enableClickOnTouch.name, function() { + it('has no tests yet'); + }); + + describe(Utils.manageCustomUI.name, function() { + it('has no tests yet'); + }); + + describe(Utils.setCustomUI.name, function() { + it('has no tests yet'); + }); + + describe(Utils.setMatomo.name, function() { + it('has no tests yet'); + }); + + describe(Utils.manageMatomo.name, function() { + it('has no tests yet'); + }); + + describe(Utils.getTriggerActionDesc.name, function() { + it('has no tests yet'); + }); +}); diff --git a/client/lib/tests/index.js b/client/lib/tests/index.js new file mode 100644 index 000000000..23051aa8c --- /dev/null +++ b/client/lib/tests/index.js @@ -0,0 +1 @@ +import './Utils.tests'; diff --git a/client/lib/utils.js b/client/lib/utils.js index c921fddca..f8f684325 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -1,40 +1,102 @@ -import { Cookies } from 'meteor/ostrio:cookies'; -const cookies = new Cookies(); - Utils = { + reload () { + // we move all window.location.reload calls into this function + // so we can disable it when running tests. + // This is because we are not allowed to override location.reload but + // we can override Utils.reload to prevent reload during tests. + window.location.reload(); + }, setBoardView(view) { currentUser = Meteor.user(); if (currentUser) { Meteor.user().setBoardView(view); - } else if (view === 'board-view-lists') { - cookies.set('boardView', 'board-view-lists'); //true } else if (view === 'board-view-swimlanes') { - cookies.set('boardView', 'board-view-swimlanes'); //true + window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true + Utils.reload(); + } else if (view === 'board-view-lists') { + window.localStorage.setItem('boardView', 'board-view-lists'); //true + Utils.reload(); } else if (view === 'board-view-cal') { - cookies.set('boardView', 'board-view-cal'); //true + window.localStorage.setItem('boardView', 'board-view-cal'); //true + Utils.reload(); + } else { + window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true + Utils.reload(); } }, unsetBoardView() { - cookies.remove('boardView'); - cookies.remove('collapseSwimlane'); + window.localStorage.removeItem('boardView'); + window.localStorage.removeItem('collapseSwimlane'); }, boardView() { currentUser = Meteor.user(); if (currentUser) { return (currentUser.profile || {}).boardView; - } else if (cookies.get('boardView') === 'board-view-lists') { - return 'board-view-lists'; - } else if (cookies.get('boardView') === 'board-view-swimlanes') { + } else if ( + window.localStorage.getItem('boardView') === 'board-view-swimlanes' + ) { return 'board-view-swimlanes'; - } else if (cookies.get('boardView') === 'board-view-cal') { + } else if ( + window.localStorage.getItem('boardView') === 'board-view-lists' + ) { + return 'board-view-lists'; + } else if (window.localStorage.getItem('boardView') === 'board-view-cal') { return 'board-view-cal'; } else { - return false; + window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true + Utils.reload(); + return 'board-view-swimlanes'; } }, + myCardsSort() { + let sort = window.localStorage.getItem('myCardsSort'); + + if (!sort || !['board', 'dueAt'].includes(sort)) { + sort = 'board'; + } + + return sort; + }, + + myCardsSortToggle() { + if (this.myCardsSort() === 'board') { + this.setMyCardsSort('dueAt'); + } else { + this.setMyCardsSort('board'); + } + }, + + setMyCardsSort(sort) { + window.localStorage.setItem('myCardsSort', sort); + Utils.reload(); + }, + + archivedBoardIds() { + const archivedBoards = []; + Boards.find({ archived: false }).forEach(board => { + archivedBoards.push(board._id); + }); + return archivedBoards; + }, + + dueCardsView() { + let view = window.localStorage.getItem('dueCardsView'); + + if (!view || !['me', 'all'].includes(view)) { + view = 'me'; + } + + return view; + }, + + setDueCardsView(view) { + window.localStorage.setItem('dueCardsView', view); + Utils.reload(); + }, + // XXX We should remove these two methods goBoardId(_id) { const board = Boards.findOne(_id); @@ -181,6 +243,21 @@ Utils = { //return false; }, + // returns if desktop drag handles are enabled + isShowDesktopDragHandles() { + const currentUser = Meteor.user(); + if (currentUser) { + return (currentUser.profile || {}).showDesktopDragHandles; + } else { + return false; + } + }, + + // returns if mini screen or desktop drag handles + isMiniScreenOrShowDesktopDragHandles() { + return this.isMiniScreen() || this.isShowDesktopDragHandles(); + }, + calculateIndexData(prevData, nextData, nItems = 1) { let base, increment; // If we drop the card to an empty column diff --git a/config/const.js b/config/const.js new file mode 100644 index 000000000..a275ffa6b --- /dev/null +++ b/config/const.js @@ -0,0 +1,61 @@ +export const ALLOWED_BOARD_COLORS = [ + 'belize', + 'nephritis', + 'pomegranate', + 'pumpkin', + 'wisteria', + 'moderatepink', + 'strongcyan', + 'limegreen', + 'midnight', + 'dark', + 'relax', + 'corteza', + 'clearblue', + 'natural', + 'modern', + 'moderndark', +]; +export const ALLOWED_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', +]; +export const TYPE_BOARD = 'board'; +export const TYPE_CARD = 'cardType-card'; +export const TYPE_LINKED_BOARD = 'cardType-linkedBoard'; +export const TYPE_LINKED_CARD = 'cardType-linkedCard'; +export const TYPE_TEMPLATE_BOARD = 'template-board'; +export const TYPE_TEMPLATE_CONTAINER = 'template-container'; +export const ALLOWED_WAIT_SPINNERS = [ + 'Bounce', + 'Cube', + 'Cube-Grid', + 'Dot', + 'Double-Bounce', + 'Rotateplane', + 'Scaleout', + 'Wave' +]; diff --git a/config/query-classes.js b/config/query-classes.js new file mode 100644 index 000000000..f86a0af21 --- /dev/null +++ b/config/query-classes.js @@ -0,0 +1,519 @@ +import { + OPERATOR_ASSIGNEE, + OPERATOR_BOARD, + OPERATOR_COMMENT, + OPERATOR_CREATED_AT, + OPERATOR_CREATOR, + OPERATOR_DUE, + OPERATOR_HAS, + OPERATOR_LABEL, + OPERATOR_LIMIT, + OPERATOR_LIST, + OPERATOR_MEMBER, + OPERATOR_MODIFIED_AT, + OPERATOR_SORT, + OPERATOR_STATUS, + OPERATOR_SWIMLANE, + OPERATOR_UNKNOWN, + OPERATOR_USER, + ORDER_ASCENDING, + ORDER_DESCENDING, + PREDICATE_ALL, + PREDICATE_ARCHIVED, + PREDICATE_ASSIGNEES, + PREDICATE_ATTACHMENT, + PREDICATE_CHECKLIST, + PREDICATE_CREATED_AT, + PREDICATE_DESCRIPTION, + PREDICATE_DUE_AT, + PREDICATE_END_AT, + PREDICATE_ENDED, + PREDICATE_MEMBERS, + PREDICATE_MODIFIED_AT, + PREDICATE_MONTH, + PREDICATE_OPEN, + PREDICATE_OVERDUE, + PREDICATE_PRIVATE, + PREDICATE_PUBLIC, + PREDICATE_QUARTER, + PREDICATE_START_AT, + PREDICATE_WEEK, + PREDICATE_YEAR, +} from './search-const'; +import Boards from '../models/boards'; +import moment from 'moment'; + +export class QueryParams { + text = ''; + + constructor(params = {}, text = '') { + this.params = params; + this.text = text; + } + + hasOperator(operator) { + return ( + this.params[operator] !== undefined && + (this.params[operator].length === undefined || + this.params[operator].length > 0) + ); + } + + addPredicate(operator, predicate) { + if (!this.hasOperator(operator)) { + this.params[operator] = []; + } + this.params[operator].push(predicate); + } + + setPredicate(operator, predicate) { + this.params[operator] = predicate; + } + + getPredicate(operator) { + if (typeof this.params[operator] === 'object') { + return this.params[operator][0]; + } else { + return this.params[operator]; + } + } + + getPredicates(operator) { + return this.params[operator]; + } + + getParams() { + return this.params; + } +} + +export class QueryErrors { + operatorTagMap = [ + [OPERATOR_BOARD, 'board-title-not-found'], + [OPERATOR_SWIMLANE, 'swimlane-title-not-found'], + [ + OPERATOR_LABEL, + label => { + if (Boards.labelColors().includes(label)) { + return { + tag: 'label-color-not-found', + value: label, + color: true, + }; + } else { + return { + tag: 'label-not-found', + value: label, + color: false, + }; + } + }, + ], + [OPERATOR_LIST, 'list-title-not-found'], + [OPERATOR_COMMENT, 'comment-not-found'], + [OPERATOR_USER, 'user-username-not-found'], + [OPERATOR_ASSIGNEE, 'user-username-not-found'], + [OPERATOR_MEMBER, 'user-username-not-found'], + [OPERATOR_CREATOR, 'user-username-not-found'], + ]; + + constructor() { + this._errors = {}; + + this.operatorTags = {}; + this.operatorTagMap.forEach(([operator, tag]) => { + this.operatorTags[operator] = tag; + }); + + this.colorMap = Boards.colorMap(); + } + + addError(operator, error) { + if (!this._errors[operator]) { + this._errors[operator] = []; + } + this._errors[operator].push(error); + } + + addNotFound(operator, value) { + if (typeof this.operatorTags[operator] === 'function') { + this.addError(operator, this.operatorTags[operator](value)); + } else { + this.addError(operator, { tag: this.operatorTags[operator], value }); + } + } + + hasErrors() { + return Object.entries(this._errors).length > 0; + } + + errors() { + const errs = []; + // eslint-disable-next-line no-unused-vars + Object.entries(this._errors).forEach(([, errors]) => { + errors.forEach(err => { + errs.push(err); + }); + }); + return errs; + } + + errorMessages() { + const messages = []; + // eslint-disable-next-line no-unused-vars + Object.entries(this._errors).forEach(([, errors]) => { + errors.forEach(err => { + messages.push(TAPi18n.__(err.tag, err.value)); + }); + }); + return messages; + } +} + +export class Query { + selector = {}; + projection = {}; + + constructor(selector, projection) { + this._errors = new QueryErrors(); + this.queryParams = new QueryParams(); + this.colorMap = Boards.colorMap(); + + if (selector) { + this.selector = selector; + } + + if (projection) { + this.projection = projection; + } + } + + hasErrors() { + return this._errors.hasErrors(); + } + + errors() { + return this._errors.errors(); + } + + errorMessages() { + return this._errors.errorMessages(); + } + + getQueryParams() { + return this.queryParams; + } + + setQueryParams(queryParams) { + this.queryParams = queryParams; + } + + addPredicate(operator, predicate) { + this.queryParams.addPredicate(operator, predicate); + } + + buildParams(queryText) { + queryText = queryText.trim(); + // eslint-disable-next-line no-console + //console.log('query:', query); + + if (!queryText) { + return; + } + + const reOperator1 = new RegExp( + '^((?[\\p{Letter}\\p{Mark}]+):|(?[#@]))(?[\\p{Letter}\\p{Mark}]+)(\\s+|$)', + 'iu', + ); + const reOperator2 = new RegExp( + '^((?[\\p{Letter}\\p{Mark}]+):|(?[#@]))(?["\']*)(?.*?)\\k(\\s+|$)', + 'iu', + ); + const reText = new RegExp('^(?\\S+)(\\s+|$)', 'u'); + const reQuotedText = new RegExp( + '^(?["\'])(?.*?)\\k(\\s+|$)', + 'u', + ); + const reNegatedOperator = new RegExp('^-(?.*)$'); + + const operators = { + 'operator-board': OPERATOR_BOARD, + 'operator-board-abbrev': OPERATOR_BOARD, + 'operator-swimlane': OPERATOR_SWIMLANE, + 'operator-swimlane-abbrev': OPERATOR_SWIMLANE, + 'operator-list': OPERATOR_LIST, + 'operator-list-abbrev': OPERATOR_LIST, + 'operator-label': OPERATOR_LABEL, + 'operator-label-abbrev': OPERATOR_LABEL, + 'operator-user': OPERATOR_USER, + 'operator-user-abbrev': OPERATOR_USER, + 'operator-member': OPERATOR_MEMBER, + 'operator-member-abbrev': OPERATOR_MEMBER, + 'operator-assignee': OPERATOR_ASSIGNEE, + 'operator-creator': OPERATOR_CREATOR, + 'operator-assignee-abbrev': OPERATOR_ASSIGNEE, + 'operator-status': OPERATOR_STATUS, + 'operator-due': OPERATOR_DUE, + 'operator-created': OPERATOR_CREATED_AT, + 'operator-modified': OPERATOR_MODIFIED_AT, + 'operator-comment': OPERATOR_COMMENT, + 'operator-has': OPERATOR_HAS, + 'operator-sort': OPERATOR_SORT, + 'operator-limit': OPERATOR_LIMIT, + }; + + const predicates = { + due: { + 'predicate-overdue': PREDICATE_OVERDUE, + }, + durations: { + 'predicate-week': PREDICATE_WEEK, + 'predicate-month': PREDICATE_MONTH, + 'predicate-quarter': PREDICATE_QUARTER, + 'predicate-year': PREDICATE_YEAR, + }, + status: { + 'predicate-archived': PREDICATE_ARCHIVED, + 'predicate-all': PREDICATE_ALL, + 'predicate-open': PREDICATE_OPEN, + 'predicate-ended': PREDICATE_ENDED, + 'predicate-public': PREDICATE_PUBLIC, + 'predicate-private': PREDICATE_PRIVATE, + }, + sorts: { + 'predicate-due': PREDICATE_DUE_AT, + 'predicate-created': PREDICATE_CREATED_AT, + 'predicate-modified': PREDICATE_MODIFIED_AT, + }, + has: { + 'predicate-description': PREDICATE_DESCRIPTION, + 'predicate-checklist': PREDICATE_CHECKLIST, + 'predicate-attachment': PREDICATE_ATTACHMENT, + 'predicate-start': PREDICATE_START_AT, + 'predicate-end': PREDICATE_END_AT, + 'predicate-due': PREDICATE_DUE_AT, + 'predicate-assignee': PREDICATE_ASSIGNEES, + 'predicate-member': PREDICATE_MEMBERS, + }, + }; + const predicateTranslations = {}; + Object.entries(predicates).forEach(([category, catPreds]) => { + predicateTranslations[category] = {}; + Object.entries(catPreds).forEach(([tag, value]) => { + predicateTranslations[category][TAPi18n.__(tag)] = value; + }); + }); + // eslint-disable-next-line no-console + // console.log('predicateTranslations:', predicateTranslations); + + const operatorMap = {}; + Object.entries(operators).forEach(([key, value]) => { + operatorMap[TAPi18n.__(key).toLowerCase()] = value; + }); + // eslint-disable-next-line no-console + // console.log('operatorMap:', operatorMap); + + let text = ''; + while (queryText) { + let m = queryText.match(reOperator1); + if (!m) { + m = queryText.match(reOperator2); + if (m) { + queryText = queryText.replace(reOperator2, ''); + } + } else { + queryText = queryText.replace(reOperator1, ''); + } + if (m) { + let op; + if (m.groups.operator) { + op = m.groups.operator.toLowerCase(); + } else { + op = m.groups.abbrev.toLowerCase(); + } + // eslint-disable-next-line no-prototype-builtins + if (operatorMap.hasOwnProperty(op)) { + const operator = operatorMap[op]; + let value = m.groups.value; + if (operator === OPERATOR_LABEL) { + if (value in this.colorMap) { + value = this.colorMap[value]; + // console.log('found color:', value); + } + } else if ( + [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes( + operator, + ) + ) { + const days = parseInt(value, 10); + let duration = null; + if (isNaN(days)) { + // duration was specified as text + if (predicateTranslations.durations[value]) { + duration = predicateTranslations.durations[value]; + let date = null; + switch (duration) { + case PREDICATE_WEEK: + // eslint-disable-next-line no-case-declarations + const week = moment().week(); + if (week === 52) { + date = moment(1, 'W'); + date.set('year', date.year() + 1); + } else { + date = moment(week + 1, 'W'); + } + break; + case PREDICATE_MONTH: + // eslint-disable-next-line no-case-declarations + const month = moment().month(); + // .month() is zero indexed + if (month === 11) { + date = moment(1, 'M'); + date.set('year', date.year() + 1); + } else { + date = moment(month + 2, 'M'); + } + break; + case PREDICATE_QUARTER: + // eslint-disable-next-line no-case-declarations + const quarter = moment().quarter(); + if (quarter === 4) { + date = moment(1, 'Q'); + date.set('year', date.year() + 1); + } else { + date = moment(quarter + 1, 'Q'); + } + break; + case PREDICATE_YEAR: + date = moment(moment().year() + 1, 'YYYY'); + break; + } + if (date) { + value = { + operator: '$lt', + value: date.format('YYYY-MM-DD'), + }; + } + } else if ( + operator === OPERATOR_DUE && + value === PREDICATE_OVERDUE + ) { + value = { + operator: '$lt', + value: moment().format('YYYY-MM-DD'), + }; + } else { + this.errors.addError(OPERATOR_DUE, { + tag: 'operator-number-expected', + value: { operator: op, value }, + }); + continue; + } + } else if (operator === OPERATOR_DUE) { + value = { + operator: '$lt', + value: moment(moment().format('YYYY-MM-DD')) + .add(days + 1, duration ? duration : 'days') + .format(), + }; + } else { + value = { + operator: '$gte', + value: moment(moment().format('YYYY-MM-DD')) + .subtract(days, duration ? duration : 'days') + .format(), + }; + } + } else if (operator === OPERATOR_SORT) { + let negated = false; + const m = value.match(reNegatedOperator); + if (m) { + value = m.groups.operator; + negated = true; + } + if (!predicateTranslations.sorts[value]) { + this.errors.addError(OPERATOR_SORT, { + tag: 'operator-sort-invalid', + value, + }); + continue; + } else { + value = { + name: predicateTranslations.sorts[value], + order: negated ? ORDER_DESCENDING : ORDER_ASCENDING, + }; + } + } else if (operator === OPERATOR_STATUS) { + if (!predicateTranslations.status[value]) { + this.errors.addError(OPERATOR_STATUS, { + tag: 'operator-status-invalid', + value, + }); + continue; + } else { + value = predicateTranslations.status[value]; + } + } else if (operator === OPERATOR_HAS) { + let negated = false; + const m = value.match(reNegatedOperator); + if (m) { + value = m.groups.operator; + negated = true; + } + if (!predicateTranslations.has[value]) { + this.errors.addError(OPERATOR_HAS, { + tag: 'operator-has-invalid', + value, + }); + continue; + } else { + value = { + field: predicateTranslations.has[value], + exists: !negated, + }; + } + } else if (operator === OPERATOR_LIMIT) { + const limit = parseInt(value, 10); + if (isNaN(limit) || limit < 1) { + this.errors.addError(OPERATOR_LIMIT, { + tag: 'operator-limit-invalid', + value, + }); + continue; + } else { + value = limit; + } + } + + this.queryParams.addPredicate(operator, value); + } else { + this.errors.addError(OPERATOR_UNKNOWN, { + tag: 'operator-unknown-error', + value: op, + }); + } + continue; + } + + m = queryText.match(reQuotedText); + if (!m) { + m = queryText.match(reText); + if (m) { + queryText = queryText.replace(reText, ''); + } + } else { + queryText = queryText.replace(reQuotedText, ''); + } + if (m) { + text += (text ? ' ' : '') + m.groups.text; + } + } + + // eslint-disable-next-line no-console + // console.log('text:', text); + this.queryParams.text = text; + + // eslint-disable-next-line no-console + console.log('queryParams:', this.queryParams); + } +} diff --git a/config/router.js b/config/router.js index ad76035b1..372f17c7e 100644 --- a/config/router.js +++ b/config/router.js @@ -14,6 +14,29 @@ FlowRouter.route('/', { Session.set('currentCard', null); Filter.reset(); + Session.set('sortBy', ''); + EscapeActions.executeAll(); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { + headerBar: 'boardListHeaderBar', + content: 'boardList', + }); + }, +}); + +FlowRouter.route('/public', { + name: 'public', + triggersEnter: [AccountsTemplates.ensureSignedIn], + action() { + Session.set('currentBoard', null); + Session.set('currentList', null); + Session.set('currentCard', null); + + Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); Utils.manageCustomUI(); @@ -38,6 +61,7 @@ FlowRouter.route('/b/:id/:slug', { // want to excape every current actions (filters, etc.) if (previousBoard !== currentBoard) { Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); } else { EscapeActions.executeUpTo('popup-close'); @@ -92,6 +116,92 @@ FlowRouter.route('/shortcuts', { }, }); +FlowRouter.route('/my-cards', { + name: 'my-cards', + triggersEnter: [AccountsTemplates.ensureSignedIn], + action() { + Filter.reset(); + Session.set('sortBy', ''); + // EscapeActions.executeAll(); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { + headerBar: 'myCardsHeaderBar', + content: 'myCards', + }); + // } + }, +}); + +FlowRouter.route('/due-cards', { + name: 'due-cards', + triggersEnter: [AccountsTemplates.ensureSignedIn], + action() { + Filter.reset(); + Session.set('sortBy', ''); + // EscapeActions.executeAll(); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { + headerBar: 'dueCardsHeaderBar', + content: 'dueCards', + }); + // } + }, +}); + +FlowRouter.route('/global-search', { + name: 'global-search', + triggersEnter: [AccountsTemplates.ensureSignedIn], + action() { + Filter.reset(); + Session.set('sortBy', ''); + // EscapeActions.executeAll(); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + DocHead.setTitle(TAPi18n.__('globalSearch-title')); + + if (FlowRouter.getQueryParam('q')) { + Session.set( + 'globalQuery', + decodeURIComponent(FlowRouter.getQueryParam('q')), + ); + } + BlazeLayout.render('defaultLayout', { + headerBar: 'globalSearchHeaderBar', + content: 'globalSearch', + }); + }, +}); + +FlowRouter.route('/broken-cards', { + name: 'broken-cards', + action() { + const brokenCardsTemplate = 'brokenCards'; + + Filter.reset(); + Session.set('sortBy', ''); + // EscapeActions.executeAll(); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { + headerBar: 'brokenCardsHeaderBar', + content: brokenCardsTemplate, + }); + }, +}); + FlowRouter.route('/import/:source', { name: 'import', triggersEnter: [AccountsTemplates.ensureSignedIn], @@ -105,6 +215,7 @@ FlowRouter.route('/import/:source', { Session.set('importSource', params.source); Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); BlazeLayout.render('defaultLayout', { headerBar: 'importHeaderBar', @@ -123,6 +234,7 @@ FlowRouter.route('/setting', { Session.set('currentCard', null); Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); }, ], @@ -145,6 +257,7 @@ FlowRouter.route('/information', { Session.set('currentCard', null); Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); }, ], @@ -166,6 +279,7 @@ FlowRouter.route('/people', { Session.set('currentCard', null); Filter.reset(); + Session.set('sortBy', ''); EscapeActions.executeAll(); }, ], @@ -177,6 +291,28 @@ FlowRouter.route('/people', { }, }); +FlowRouter.route('/admin-reports', { + name: 'admin-reports', + triggersEnter: [ + AccountsTemplates.ensureSignedIn, + () => { + Session.set('currentBoard', null); + Session.set('currentList', null); + Session.set('currentCard', null); + + Filter.reset(); + Session.set('sortBy', ''); + EscapeActions.executeAll(); + }, + ], + action() { + BlazeLayout.render('defaultLayout', { + headerBar: 'settingHeaderBar', + content: 'adminReports', + }); + }, +}); + FlowRouter.notFound = { action() { BlazeLayout.render('defaultLayout', { content: 'notFound' }); diff --git a/config/search-const.js b/config/search-const.js new file mode 100644 index 000000000..5a7f54b6b --- /dev/null +++ b/config/search-const.js @@ -0,0 +1,42 @@ +export const DEFAULT_LIMIT = 25; +export const OPERATOR_ASSIGNEE = 'assignees'; +export const OPERATOR_COMMENT = 'comment'; +export const OPERATOR_CREATED_AT = 'createdAt'; +export const OPERATOR_CREATOR = 'userId'; +export const OPERATOR_DUE = 'dueAt'; +export const OPERATOR_BOARD = 'board'; +export const OPERATOR_HAS = 'has'; +export const OPERATOR_LABEL = 'label'; +export const OPERATOR_LIMIT = 'limit'; +export const OPERATOR_LIST = 'list'; +export const OPERATOR_MEMBER = 'members'; +export const OPERATOR_MODIFIED_AT = 'modifiedAt'; +export const OPERATOR_SORT = 'sort'; +export const OPERATOR_STATUS = 'status'; +export const OPERATOR_SWIMLANE = 'swimlane'; +export const OPERATOR_UNKNOWN = 'unknown'; +export const OPERATOR_USER = 'user'; +export const ORDER_ASCENDING = 'asc'; +export const ORDER_DESCENDING = 'des'; +export const PREDICATE_ALL = 'all'; +export const PREDICATE_ARCHIVED = 'archived'; +export const PREDICATE_ASSIGNEES = 'assignees'; +export const PREDICATE_ATTACHMENT = 'attachment'; +export const PREDICATE_CHECKLIST = 'checklist'; +export const PREDICATE_CREATED_AT = 'createdAt'; +export const PREDICATE_DESCRIPTION = 'description'; +export const PREDICATE_DUE_AT = 'dueAt'; +export const PREDICATE_END_AT = 'endAt'; +export const PREDICATE_ENDED = 'ended'; +export const PREDICATE_MEMBERS = 'members'; +export const PREDICATE_MODIFIED_AT = 'modifiedAt'; +export const PREDICATE_MONTH = 'month'; +export const PREDICATE_OPEN = 'open'; +export const PREDICATE_OVERDUE = 'overdue'; +export const PREDICATE_PRIVATE = 'private'; +export const PREDICATE_PUBLIC = 'public'; +export const PREDICATE_QUARTER = 'quarter'; +export const PREDICATE_START_AT = 'startAt'; +export const PREDICATE_SYSTEM = 'system'; +export const PREDICATE_WEEK = 'week'; +export const PREDICATE_YEAR = 'year'; diff --git a/docker-compose.yml b/docker-compose.yml index 34251252e..aa4e0196d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,13 +13,12 @@ version: '2' # see https://github.com/wekan/wekan/wiki/Backup # 1) Stop Wekan: # docker-compose stop -# 2) Download new version: -# docker-compose pull wekan -# 3) If you have more networks for VPN etc as described at bottom of -# this config, download for them too: -# docker-compose pull wekan2 +# 2) Remove old Wekan app (wekan-app only, not that wekan-db container that has all your data) +# docker rm wekan-app +# 3) Get newest docker-compose.yml from https://github.com/wekan/wekan to have correct image, +# for example: "image: quay.io/wekan/wekan" or version tag "image: quay.io/wekan/wekan:v4.52" # 4) Start Wekan: -# docker-compose start +# docker-compose up -d #---------------------------------------------------------------------------------- # ==== OPTIONAL: DEDICATED DOCKER USER ==== # 1) Optionally create a dedicated user for Wekan, for example: @@ -38,7 +37,7 @@ version: '2' # sudo service docker start # ---------------------------------------------------------------------------------- # ==== USAGE OF THIS docker-compose.yml ==== -# 1) For seeing does Wekan work, try this and check with your webbroser: +# 1) For seeing does Wekan work, try this and check with your web browser: # docker-compose up # 2) Stop Wekan and start Wekan in background: # docker-compose stop @@ -69,7 +68,7 @@ version: '2' # docker exec -it wekan-db bash # # 3) and data directory # cd /data -# # 4) Remove previos dump +# # 4) Remove previous dump # rm -rf dump # # 5) Exit db container # exit @@ -92,15 +91,21 @@ services: wekandb: #------------------------------------------------------------------------------------- # ==== MONGODB AND METEOR VERSION ==== - # a) For Wekan Meteor 1.8.x version at master branch, use mongo 4.x - image: mongo:latest - # b) For Wekan Meteor 1.6.x version at devel branch. - # Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x - #image: mongo:3.2.21 + # a) mongodb latest, like 3.2 - 4.4 or newer https://hub.docker.com/_/mongo?tab=description + # 2020-12-03: + # - Mongo image copied from Docker Hub mongo:4.4.2-bionic to Quay + # to avoid Docker Hub rate limits. + # Quay image does work: + # image: quay.io/wekan/mongo:4.4.2-bionic + # Docker Hub MongoDB image does work: + image: mongo:4.4 #------------------------------------------------------------------------------------- container_name: wekan-db restart: always - command: mongod --oplogSize 128 + # command: mongod --oplogSize 128 + # Syslog: mongod --syslog --oplogSize 128 --quiet + # Disable MongoDB logs: + command: mongod --logpath /dev/null --oplogSize 128 --quiet networks: - wekan-tier expose: @@ -112,16 +117,12 @@ services: wekan: #------------------------------------------------------------------------------------- # ==== MONGODB AND METEOR VERSION ==== - # NOTE: Quay is currently not updated, use Docker Hub image below c) - # a) For Wekan Meteor 1.8.x version at master branch, - # using https://quay.io/wekan/wekan automatic builds - #image: quay.io/wekan/wekan - image: localhost/wekan:latest - # b) Using specific Meteor 1.6.x version tag: - # image: quay.io/wekan/wekan:v1.95 - # c) Using Docker Hub automatic builds https://hub.docker.com/r/wekanteam/wekan - #image: wekanteam/wekan - # image: wekanteam/wekan:v2.99 + # a) Quay automatic builds do work work, https://quay.io/wekan/wekan + image: quay.io/wekan/wekan + # b) Using specific version tag: + # image: quay.io/wekan/wekan:v4.52 + # c) Docker Hub builds do work https://hub.docker.com/r/wekanteam/wekan + # image: wekanteam/wekan #------------------------------------------------------------------------------------- container_name: wekan-app restart: always @@ -133,14 +134,6 @@ services: #build: # context: . # dockerfile: Dockerfile - # args: - # - NODE_VERSION=${NODE_VERSION} - # - METEOR_RELEASE=${METEOR_RELEASE} - # - NPM_VERSION=${NPM_VERSION} - # - ARCHITECTURE=${ARCHITECTURE} - # - SRC_PATH=${SRC_PATH} - # - METEOR_EDGE=${METEOR_EDGE} - # - USE_EDGE=${USE_EDGE} #------------------------------------------------------------------------------------- ports: # Docker outsideport:insideport. Do not add anything extra here. @@ -181,7 +174,7 @@ services: # that if you're using more than 1 instance of Wekan # (or any MeteorJS based tool) you're supposed to set # MONGO_OPLOG_URL as an environment variable. - # Without setting it, Meteor will perform a pull-and-diff + # Without setting it, Meteor will perform a poll-and-diff # update of it's dataset. With it, Meteor will update from # the OPLOG. See here # https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908 @@ -216,6 +209,9 @@ services: # There is Feature Request: Logging date and time of all activity with summary reports, # and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598 #--------------------------------------------------------------- + # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== + #- RESULTS_PER_PAGE=20 + #--------------------------------------------------------------- # ==== WEKAN API AND EXPORT BOARD ==== # Wekan Export Board works when WITH_API=true. # https://github.com/wekan/wekan/wiki/REST-API @@ -241,11 +237,6 @@ services: # https://github.com/wekan/wekan/pull/2560 - RICHER_CARD_COMMENT_EDITOR=false #--------------------------------------------------------------- - # ==== MOUSE SCROLL ==== - # https://github.com/wekan/wekan/issues/2949 - - SCROLLINERTIA=0 - - SCROLLAMOUNT=auto - #--------------------------------------------------------------- # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== # https://github.com/wekan/wekan/issues/2518 - CARD_OPENED_WEBHOOK_ENABLED=false @@ -255,6 +246,11 @@ services: #-MAX_IMAGE_PIXEL=1024 #-IMAGE_COMPRESS_RATIO=80 #--------------------------------------------------------------- + # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== + # Number of days after a notification is read before we remove it. + # Default: 2 + #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + #--------------------------------------------------------------- # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== # https://github.com/wekan/wekan/pull/2541 # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", @@ -285,7 +281,7 @@ services: #- NOTIFY_DUE_AT_HOUR_OF_DAY=8 #----------------------------------------------------------------- # ==== EMAIL NOTIFICATION TIMEOUT, ms ===== - # Defaut: 30000 ms = 30s + # Default: 30000 ms = 30s #- EMAIL_NOTIFICATION_TIMEOUT=30000 #----------------------------------------------------------------- # ==== CORS ===== @@ -316,12 +312,15 @@ services: #- TRUSTED_URL=https://intra.example.com #----------------------------------------------------------------- # ==== OUTGOING WEBHOOKS ==== - # What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId . + # What to send to Outgoing Webhook, or leave out. If commented out the default values will be: cardId,listId,oldListId,boardId,comment,user,card,commentId,swimlaneId,customerField,customFieldValue #- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId #----------------------------------------------------------------- # ==== Debug OIDC OAuth2 etc ==== #- DEBUG=true #----------------------------------------------------------------- + # ==== OAUTH2 ORACLE on premise identity manager OIM ==== + #- ORACLE_OIM_ENABLED=true + #----------------------------------------------------------------- # ==== OAUTH2 AZURE ==== # https://github.com/wekan/wekan/wiki/Azure # 1) Register the application with Azure. Make sure you capture @@ -329,6 +328,10 @@ services: # 2) Configure the environment variables. This differs slightly # by installation type, but make sure you have the following: #- OAUTH2_ENABLED=true + # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299 + #- OAUTH2_CA_CERT=ABCD1234 + # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. + #- OAUTH2_ADFS_ENABLED=false # OAuth2 login style: popup or redirect. #- OAUTH2_LOGIN_STYLE=redirect # Application GUID captured during app registration: @@ -345,12 +348,13 @@ services: #- OAUTH2_USERNAME_MAP=email # The claim name you want to map to the full name field: #- OAUTH2_FULLNAME_MAP=name - # Tthe claim name you want to map to the email field: + # The claim name you want to map to the email field: #- OAUTH2_EMAIL_MAP=email #----------------------------------------------------------------- # ==== OAUTH2 Nextcloud ==== - # 1) Register the application with Nextcloud: https://your.nextcloud/settings/admin/security + # 1) Register the application with Nextcloud: https://your.nextcloud/index.php/settings/admin/security # Make sure you capture the application ID as well as generate a secret key. + # Use https://your.wekan/_oauth/oidc for the redirect URI. # 2) Configure the environment variables. This differs slightly # by installation type, but make sure you have the following: #- OAUTH2_ENABLED=true @@ -370,7 +374,7 @@ services: #- OAUTH2_USERNAME_MAP=id # The claim name you want to map to the full name field: #- OAUTH2_FULLNAME_MAP=display-name - # Tthe claim name you want to map to the email field: + # The claim name you want to map to the email field: #- OAUTH2_EMAIL_MAP=email #----------------------------------------------------------------- # ==== OAUTH2 KEYCLOAK ==== @@ -461,8 +465,10 @@ services: # If the LDAP needs a user account to search - LDAP_AUTHENTIFICATION=true # - # The search user DN - #- LDAP_AUTHENTIFICATION_USERDN=maassens@verband.creditreform.de + # The search user DN - You need quotes when you have spaces in parameters + # 2 examples: + #- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan + #- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com # # The password for the search user #- LDAP_AUTHENTIFICATION_PASSWORD= @@ -488,7 +494,13 @@ services: #- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE----- # # Reject Unauthorized Certificate - - LDAP_REJECT_UNAUTHORIZED=false + #- LDAP_REJECT_UNAUTHORIZED=false + # + # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key). + #- LDAP_USER_AUTHENTICATION=true + # + # Which field is used to find the user for the user authentication. Default: uid. + #- LDAP_USER_AUTHENTICATION_FIELD=uid # # Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed - LDAP_USER_SEARCH_FILTER=(objectClass=user) @@ -515,7 +527,8 @@ services: # # - LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE= # - # - LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT= + # The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn is saved as value into the attribute. + #- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT= # # - LDAP_GROUP_FILTER_GROUP_NAME= # @@ -549,7 +562,7 @@ services: # - LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"} # - #- LDAP_SYNC_GROUP_ROLES='' + #- LDAP_SYNC_GROUP_ROLES= # # The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP # example : @@ -585,6 +598,29 @@ services: # example : LOGOUT_ON_MINUTES=55 #- LOGOUT_ON_MINUTES= #------------------------------------------------------------------- + # Hide password login form + # - PASSWORD_LOGIN_ENABLED=true + #------------------------------------------------------------------- + #- CAS_ENABLED=true + #- CAS_BASE_URL=https://cas.example.com/cas + #- CAS_LOGIN_URL=https://cas.example.com/login + #- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + #--------------------------------------------------------------------- + #- SAML_ENABLED=true + #- SAML_PROVIDER= + #- SAML_ENTRYPOINT= + #- SAML_ISSUER= + #- SAML_CERT= + #- SAML_IDPSLO_REDIRECTURL= + #- SAML_PRIVATE_KEYFILE= + #- SAML_PUBLIC_CERTFILE= + #- SAML_IDENTIFIER_FORMAT= + #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= + #- SAML_ATTRIBUTES= + #--------------------------------------------------------------------- + # Wait spinner to use + # - WAIT_SPINNER=Bounce + #--------------------------------------------------------------------- depends_on: - wekandb diff --git a/find-replace.sh b/find-replace.sh new file mode 100755 index 000000000..522affab9 --- /dev/null +++ b/find-replace.sh @@ -0,0 +1,5 @@ + +# Recursive find/replace. +# Syntax: ./find-replace.sh searchtext replacetext + +egrep -lRZ '$1' . | xargs -0 -l sed -i -e 's/$1/$2/g' diff --git a/fix-download-unicode/cfs_access-point.txt b/fix-download-unicode/cfs_access-point.txt index de1c6c769..968e94484 100644 --- a/fix-download-unicode/cfs_access-point.txt +++ b/fix-download-unicode/cfs_access-point.txt @@ -4,11 +4,11 @@ var Meteor = Package.meteor.Meteor; var global = Package.meteor.global; var meteorEnv = Package.meteor.meteorEnv; -var FS = Package['cfs:base-package'].FS; +var FS = Package['wekan-cfs-base-package'].FS; var check = Package.check.check; var Match = Package.check.Match; var EJSON = Package.ejson.EJSON; -var HTTP = Package['cfs:http-methods'].HTTP; +var HTTP = Package['wekan-cfs-http-methods'].HTTP; /* Package-scope variables */ var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls; @@ -25,7 +25,7 @@ var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMou //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // -// packages/cfs:access-point/access-point-common.js // +// packages/wekan-cfs-access-point/access-point-common.js // // // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -217,7 +217,7 @@ FS.File.prototype.url = function(options) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // -// packages/cfs:access-point/access-point-handlers.js // +// packages/wekan-cfs-access-point/access-point-handlers.js // // // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -537,7 +537,7 @@ FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // -// packages/cfs:access-point/access-point-server.js // +// packages/wekan-cfs-access-point/access-point-server.js // // // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -909,6 +909,6 @@ Meteor.startup(function () { /* Exports */ if (typeof Package === 'undefined') Package = {}; -Package['cfs:access-point'] = {}; +Package['wekan-cfs-access-point'] = {}; })(); diff --git a/helm/wekan/Chart.yaml b/helm/wekan/Chart.yaml index ffd164bfb..b7b508278 100644 --- a/helm/wekan/Chart.yaml +++ b/helm/wekan/Chart.yaml @@ -1,7 +1,6 @@ name: wekan -version: 1.0.0 -appVersion: 2.x.x -kubeVersion: "^1.8.0-0" +version: 1.0.3 +apiVersion: v1 description: Open Source kanban home: https://wekan.github.io/ icon: https://wekan.github.io/wekan-logo.svg @@ -10,4 +9,6 @@ sources: maintainers: - name: technotaff email: github@randall.cc + - name: jiangytcn + email: jiangyt.cn@gmail.com engine: gotpl diff --git a/helm/wekan/README.md b/helm/wekan/README.md index d3af930cb..38cb5fedd 100644 --- a/helm/wekan/README.md +++ b/helm/wekan/README.md @@ -56,3 +56,10 @@ mongodb-replicaset: This section controls the scale of the MongoDB redundant Replica Set. **replicas:** This is the number of MongoDB instances to include in the set. You can set this to 1 for a single server - this will still allow you to scale-up later with a helm upgrade. + +### Install OCP route +If you use this chart to deploy Wekan on an OCP cluster, you can create route instead of ingress with following command: + +``` bash +$ helm template --set route.enabled=true,ingress.enabled=false values.yaml . | oc apply -f- +``` diff --git a/helm/wekan/requirements.yaml b/helm/wekan/requirements.yaml index d19fc638f..d6b6b2e76 100644 --- a/helm/wekan/requirements.yaml +++ b/helm/wekan/requirements.yaml @@ -1,5 +1,5 @@ dependencies: -- name: mongodb-replicaset - version: 3.11.x - repository: "https://kubernetes-charts.storage.googleapis.com/" - condition: mongodb-replicaset.enabled +- name: mongodb + version: 10.0.x + repository: "https://charts.bitnami.com/bitnami" + condition: mongodb.enabled diff --git a/helm/wekan/templates/_helpers.tpl b/helm/wekan/templates/_helpers.tpl index 7f333301e..5ee1b10e9 100644 --- a/helm/wekan/templates/_helpers.tpl +++ b/helm/wekan/templates/_helpers.tpl @@ -62,21 +62,27 @@ Create the name of the service account to use for the api component Create a default fully qualified mongodb-replicaset name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} -{{- define "wekan.mongodb-replicaset.fullname" -}} -{{- $name := default "mongodb-replicaset" (index .Values "mongodb-replicaset" "nameOverride") -}} +{{- define "wekan.mongodb.svcname" -}} +{{- $name := default "mongodb" (index .Values "mongodb" "nameOverride") -}} +{{- if eq .Values.mongodb.architecture "replicaset" }} +{{- printf "%s-%s-headless" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{- end -}} {{/* Create the MongoDB URL. If MongoDB is installed as part of this chart, use k8s service discovery, else use user-provided URL. */}} -{{- define "mongodb-replicaset.url" -}} -{{- if (index .Values "mongodb-replicaset" "enabled") -}} -{{- $count := (int (index .Values "mongodb-replicaset" "replicas")) -}} +{{- define "mongodb.url" -}} +{{- if (index .Values "mongodb" "enabled") -}} +{{- $count := (int (index .Values "mongodb" "replicaCount")) -}} {{- $release := .Release.Name -}} -mongodb://{{ $release }}-mongodb-replicaset:27017/admin?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }} +{{- $replicaSetName := (index .Values "mongodb" "replicaSetName") -}} +{{- $mongodbSvcName := include "wekan.mongodb.svcname" . -}} +mongodb://{{- range $v := until $count }}{{ $release }}-mongodb-{{ $v }}.{{ $mongodbSvcName }}:27017{{ if ne $v (sub $count 1) }},{{- end -}}{{- end -}}/{{ .Values.dbname }}?replicaSet={{ $replicaSetName }} {{- else -}} -{{- index .Values "mongodb-replicaset" "url" -}} +{{- index .Values "mongodb" "url" -}} {{- end -}} {{- end -}} diff --git a/helm/wekan/templates/deployment.yaml b/helm/wekan/templates/deployment.yaml index e5bf2018a..c7f192f1e 100644 --- a/helm/wekan/templates/deployment.yaml +++ b/helm/wekan/templates/deployment.yaml @@ -36,7 +36,22 @@ spec: - name: ROOT_URL value: {{ .Values.root_url | default "https://wekan.local" | quote }} - name: MONGO_URL - value: "{{ template "mongodb-replicaset.url" . }}" + value: "{{ template "mongodb.url" . }}" + {{- range $key := .Values.env }} + {{- if .value }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- end }} + {{- range $key := .Values.secretEnv }} + {{- if .value }} + - name: {{ .name }} + valueFrom: + secretKeyRef: + name: {{ template "wekan.fullname" $ }}-secret + key: {{ .name }} + {{- end }} + {{- end }} livenessProbe: httpGet: path: / diff --git a/helm/wekan/templates/ingress.yaml b/helm/wekan/templates/ingress.yaml index dd85ef550..6904cdfa7 100644 --- a/helm/wekan/templates/ingress.yaml +++ b/helm/wekan/templates/ingress.yaml @@ -2,7 +2,7 @@ {{- $fullName := include "wekan.fullname" . -}} {{- $servicePort := .Values.service.port -}} {{- $ingressPath := .Values.ingress.path -}} -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: {{ $fullName }} @@ -27,6 +27,9 @@ spec: secretName: {{ .secretName }} {{- end }} {{- end }} + backend: + serviceName: {{ $fullName }} + servicePort: 8080 rules: {{- range .Values.ingress.hosts }} - host: {{ . }} @@ -35,6 +38,6 @@ spec: - path: {{ $ingressPath }} backend: serviceName: {{ $fullName }} - servicePort: 80 + servicePort: 8080 {{- end }} {{- end }} diff --git a/helm/wekan/templates/route.yaml b/helm/wekan/templates/route.yaml new file mode 100644 index 000000000..9a55591d1 --- /dev/null +++ b/helm/wekan/templates/route.yaml @@ -0,0 +1,23 @@ +{{- if .Values.route.enabled -}} +{{- $fullName := include "wekan.fullname" . -}} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + annotations: + haproxy.router.openshift.io/timeout: 4m + openshift.io/host.generated: "true" + labels: + app: {{ template "wekan.name" . }} + service: {{ template "wekan.name" . }} + name: {{ template "wekan.name" . }} +spec: + port: + targetPort: http + tls: + termination: edge + to: + kind: Service + name: {{ template "wekan.name" . }} + weight: 100 + wildcardPolicy: None + {{- end }} \ No newline at end of file diff --git a/helm/wekan/templates/secret.yaml b/helm/wekan/templates/secret.yaml new file mode 100644 index 000000000..3674692cd --- /dev/null +++ b/helm/wekan/templates/secret.yaml @@ -0,0 +1,11 @@ +{{ if .Values.secretEnv }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "wekan.fullname" $ }}-secret +type: Opaque +data: + {{- range $key := .Values.secretEnv }} + {{ $key.name }}: {{ $key.value | b64enc }} + {{- end}} +{{ end }} diff --git a/helm/wekan/templates/secrets.yaml b/helm/wekan/templates/secrets.yaml deleted file mode 100644 index 79ae3d489..000000000 --- a/helm/wekan/templates/secrets.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "wekan.fullname" . }} - labels: - app: {{ template "wekan.name" . }} - chart: {{ template "wekan.chart" . }} - component: wekan - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} -type: Opaque -data: - accessKey: {{ .Values.credentials.accessKey | b64enc }} - secretKey: {{ .Values.credentials.secretKey | b64enc }} diff --git a/helm/wekan/templates/serviceaccount.yaml b/helm/wekan/templates/serviceaccount.yaml index 58696cb6e..543e58d46 100644 --- a/helm/wekan/templates/serviceaccount.yaml +++ b/helm/wekan/templates/serviceaccount.yaml @@ -2,6 +2,10 @@ apiVersion: v1 kind: ServiceAccount metadata: +{{- if .Values.serviceAccounts.annotations }} + annotations: +{{ .Values.serviceAccounts.annotations | indent 4}} +{{- end }} labels: app: {{ template "wekan.name" . }} chart: {{ template "wekan.chart" . }} diff --git a/helm/wekan/templates/tests/test-cloudserver.yaml b/helm/wekan/templates/tests/test-cloudserver.yaml deleted file mode 100644 index a1db72890..000000000 --- a/helm/wekan/templates/tests/test-cloudserver.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{ template "wekan.fullname" . }}-test - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: {{ template "wekan.fullname" . }}-test - imagePullPolicy: IfNotPresent - image: "docker.io/mesosphere/aws-cli:1.14.5" - command: - - sh - - -c - - aws s3 --endpoint-url=http://{{ include "wekan.fullname" . }} --region=us-east-1 ls - env: - - name: AWS_ACCESS_KEY_ID - valueFrom: - secretKeyRef: - name: {{ template "wekan.fullname" . }} - key: accessKey - - name: AWS_SECRET_ACCESS_KEY - valueFrom: - secretKeyRef: - name: {{ template "wekan.fullname" . }} - key: secretKey - restartPolicy: Never diff --git a/helm/wekan/values.yaml b/helm/wekan/values.yaml index adc2c855c..280289187 100644 --- a/helm/wekan/values.yaml +++ b/helm/wekan/values.yaml @@ -8,6 +8,7 @@ serviceAccounts: create: true name: "" + annotations: "" ## Wekan image configuration ## @@ -20,20 +21,25 @@ image: ## replicaCount: 1 - -## Specify wekan credentials -## -credentials: - accessKey: access-key - secretKey: secret-key +dbname: wekan ## Specify additional environmental variables for the Deployment ## -env: {} +env: + - name: "" + value: "" + +## Specify additional secret environmental variables for the +## Deployment. These can e.g. be provided by a Secret and allow +## to store passwords separately +## +secretEnv: {} + # - name: "" + # value: "" service: type: NodePort - port: 80 + port: 8080 annotations: {} # prometheus.io/scrape: "true" # prometheus.io/port: "8000" @@ -59,7 +65,10 @@ ingress: # hosts: # - wekan-example.local -resources: +route: + enabled: false + +resources: requests: memory: 128Mi cpu: 300m @@ -98,13 +107,10 @@ autoscaling: # MongoDB: # ------------------------------------------------------------------------------ -mongodb-replicaset: +mongodb: enabled: true - replicas: 3 + architecture: replicaset + replicaCount: 3 replicaSetName: rs0 - securityContext: - runAsUser: 1000 - fsGroup: 1000 - runAsNonRoot: true - #image: - # tag: 3.2.21 + auth: + enabled: false diff --git a/i18n/ar-EG.i18n.json b/i18n/ar-EG.i18n.json new file mode 100644 index 000000000..f596968c0 --- /dev/null +++ b/i18n/ar-EG.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Accept", + "act-activity-notify": "Activity Notification", + "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "created board __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Actions", + "activities": "Activities", + "activity": "Activity", + "activity-added": "added %s to %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be private.", + "board-public-info": "This board will be public.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by logging in.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after start", + "add-custom-html-before-body-end": "Add Custom HTML before end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index ce02740be..da925a737 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -1,6 +1,6 @@ { "accept": "قبول", - "act-activity-notify": "اشعارات النشاط", + "act-activity-notify": "اشعار النشاط", "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -74,18 +74,25 @@ "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-deleteComment": "تعليق محذوف %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "إضافة مرفق", "add-board": "إضافة لوحة", + "add-template": "Add Template", "add-card": "إضافة بطاقة", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", + "add-subtask": "إضافة مهمة فرعية", "add-checklist": "إضافة قائمة تدقيق", "add-checklist-item": "إضافة عنصر إلى قائمة التحقق", "add-cover": "إضافة غلاف", "add-label": "إضافة ملصق", "add-list": "إضافة قائمة", - "add-members": "تعيين أعضاء", + "add-members": "إضافة أعضاء", "added": "أُضيف", "addMemberPopup-title": "الأعضاء", "admin": "المدير", @@ -111,8 +118,10 @@ "restore-board": "استعادة اللوحة", "no-archived-boards": "لا توجد لوحات في الأرشيف.", "archives": "أرشيف", - "template": "Template", - "templates": "Templates", + "template": "نموذج", + "templates": "نماذج", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "تعيين عضو", "attached": "أُرفق)", "attachment": "مرفق", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "تريد حذف المرفق ?", "attachments": "المرفقات", "auto-watch": "مراقبة لوحات تلقائيا عندما يتم إنشاؤها", - "avatar-too-big": "الصورة الرمزية كبيرة جدا (70 كيلوبايت كحد أقصى)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "رجوع", "board-change-color": "تغيير اللومr", "board-nb-stars": "%s نجوم", "board-not-found": "لوحة مفقودة", "board-private-info": "سوف تصبح هذه اللوحة <strong>خاصة</strong>", "board-public-info": "سوف تصبح هذه اللوحة <strong>عامّة</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "تعديل خلفية الشاشة", "boardChangeTitlePopup-title": "إعادة تسمية اللوحة", "boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية", @@ -138,6 +148,7 @@ "board-view-cal": "التقويم", "board-view-swimlanes": "خطوط السباحة", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "القائمات", "bucket-example": "مثل « todo list » على سبيل المثال", "cancel": "إلغاء", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "إرفاق من", "cardCustomField-datePopup-title": "تغير التاريخ", "cardCustomFieldsPopup-title": "تعديل الحقل المعدل", + "cardStartVotingPopup-title": "ابدأ تصويت", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "مع", + "vote-against": "ضد", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "حذف البطاقة ?", "cardDetailsActionsPopup-title": "إجراءات على البطاقة", "cardLabelsPopup-title": "علامات", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "بطاقات", "cards-count": "بطاقات", + "cards-count-one": "بطاقة", "casSignIn": "تسجيل الدخول مع CAS", "cardType-card": "بطاقة", "cardType-linkedCard": "البطاقة المرتبطة", @@ -183,7 +228,7 @@ "changePasswordPopup-title": "تغيير كلمة المرور", "changePermissionsPopup-title": "تعديل الصلاحيات", "changeSettingsPopup-title": "تغيير الاعدادات", - "subtasks": "Subtasks", + "subtasks": "المهمات الفرعية", "checklists": "قوائم التّدقيق", "click-to-star": "اضغط لإضافة اللوحة للمفضلة.", "click-to-unstar": "اضغط لحذف اللوحة من المفضلة.", @@ -191,12 +236,13 @@ "close": "غلق", "close-board": "غلق اللوحة", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-darkgreen": "اخضر غامق", + "color-gold": "ذهبي", + "color-gray": "رمادي", "color-green": "green", "color-indigo": "indigo", "color-lime": "lime", @@ -211,17 +257,17 @@ "color-purple": "purple", "color-red": "red", "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-silver": "فضي", "color-sky": "sky", "color-slateblue": "slateblue", - "color-white": "white", + "color-white": "أبيض", "color-yellow": "yellow", "unset-color": "Unset", "comment": "تعليق", "comment-placeholder": "أكتب تعليق", "comment-only": "التعليق فقط", "comment-only-desc": "يمكن التعليق على بطاقات فقط.", - "no-comments": "No comments", + "no-comments": "لا يوجد تعليقات", "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", @@ -239,19 +285,21 @@ "createBoardPopup-title": "إنشاء لوحة", "chooseBoardSourcePopup-title": "استيراد لوحة", "createLabelPopup-title": "إنشاء علامة", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", + "createCustomField": "انشاء حقل", + "createCustomFieldPopup-title": "انشاء حقل", "current": "الحالي", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "تاريخ", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", "custom-field-dropdown-options": "List Options", "custom-field-dropdown-options-placeholder": "Press enter to add more options", "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", + "custom-field-number": "رقم", + "custom-field-text": "نص", "custom-fields": "Custom Fields", "date": "تاريخ", "decline": "Decline", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "لا يمكنك دعوة نفسك", "error-user-notCreated": "This user is not created", "error-username-taken": "إسم المستخدم مأخوذ مسبقا", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "البريد الإلكتروني مأخوذ بالفعل", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "تصفية", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "مسح التصفية", + "filter-labels-label": "Filter by label", "filter-no-label": "لا يوجد ملصق", + "filter-member-label": "Filter by member", "filter-no-member": "ليس هناك أي عضو", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "التصفية تشتغل", "filter-on-desc": "أنت بصدد تصفية بطاقات هذه اللوحة. اضغط هنا لتعديل التصفية.", "filter-to-selection": "تصفية بالتحديد", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "الإسم الكامل", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "إنشاء لوحة", "home": "الرئيسية", "import": "Import", - "link": "Link", + "impersonate-user": "Impersonate user", + "link": "رابط", "import-board": "استيراد لوحة", "import-board-c": "استيراد لوحة", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "من تريلو", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "رسم خريطة الأعضاء", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "تحديد بطاقات هذه القائمة", "set-color-list": "Set Color", "listActionPopup-title": "قائمة الإجراءات", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "المزيد", "link-list": "رابط إلى هذه القائمة", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "التحرك إلى الأعلى", "moveSelectionPopup-title": "Move selection", "multi-selection": "تحديد أكثر من واحدة", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "مكتوم", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "بحث", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "اختيار اللون", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "تصفية بطاقاتي", "shortcut-show-shortcuts": "عرض قائمة الإختصارات ،تلك", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "إظهار-إخفاء الشريط الجانبي للوحة", "show-cards-minimum-count": "إظهار عدد البطاقات إذا كانت القائمة تتضمن أكثر من", "sidebar-open": "فتح الشريط الجانبي", @@ -466,22 +552,30 @@ "this-board": "هذه اللوحة", "this-card": "هذه البطاقة", "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", + "overtime-hours": "وقت اضافي (ساعات)", + "overtime": "وقت اضافي", "has-overtime-cards": "Has overtime cards", "has-spenttime-cards": "Has spent time cards", "time": "الوقت", "title": "عنوان", "tracking": "تتبع", "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", + "type": "النوع", "unassign-member": "إلغاء تعيين العضو", "unsaved-description": "لديك وصف غير محفوظ", "unwatch": "غير مُشاهد", "upload": "Upload", "upload-avatar": "رفع صورة شخصية", "uploaded-avatar": "تم رفع الصورة الشخصية", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "اسم المستخدم", + "import-usernames": "Import Usernames", "view-it": "شاهدها", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "مُشاهد", @@ -548,12 +642,13 @@ "OS_Totalmem": "الذاكرة الكلية لنظام التشغيل", "OS_Type": "نوع نظام التشغيل", "OS_Uptime": "مدة تشغيل نظام التشغيل", - "days": "days", + "days": "أيام", "hours": "الساعات", "minutes": "الدقائق", "seconds": "الثواني", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "نعم", "no": "لا", @@ -561,20 +656,22 @@ "accounts-allowEmailChange": "السماح بتغيير البريد الإلكتروني", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", - "active": "Active", + "active": "نشط", "card-received": "Received", "card-received-on": "Received on", "card-end": "End", "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", + "setCardColorPopup-title": "حدد اللون", + "setCardActionsColorPopup-title": "اختر لوناً", + "setSwimlaneColorPopup-title": "اختر لوناً", + "setListColorPopup-title": "اختر لوناً", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "بطاقة", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "رقم", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "المنشئ", + "filesReportTitle": "تقارير الملفات", + "orphanedFilesReportTitle": "تقارير الملفات المعزولة", + "reports": "تقارير", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/bg.i18n.json b/i18n/bg.i18n.json index 52ab8b4f3..fdee8fcdf 100644 --- a/i18n/bg.i18n.json +++ b/i18n/bg.i18n.json @@ -1,25 +1,25 @@ { "accept": "Приемам", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-activity-notify": "Известия за Активност", + "act-addAttachment": "добавен е прикачен файл __attachment__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-deleteAttachment": "изтрит е прикачен файл __attachment__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addSubtask": "добавена е под-задача __subtask__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addLabel": "Добавено е име __label__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addedLabel": "Добавено е име __label__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-removeLabel": "Премахнато е име __label__ от карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-removedLabel": "Премахнато е име __label__ от карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addChecklist": "добавен списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addChecklistItem": "добавен елемент от списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-removeChecklist": "премахнат елемент от списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-removeChecklistItem": "премахнат елемент __checklistItem__ от списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-checkedItem": "отметнат елемент __checklistItem__ от списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-uncheckedItem": "неотметнат е елемент __checklistItem__ от списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-completeChecklist": "изпълнен е списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-uncompleteChecklist": "неизпълнен списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", + "act-addComment": "коментирано на карта __card__: __comment__ в списък __list__ от лента __swimlane__ към дъска __board__", "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", + "act-createBoard": "създадена е дъска __board__", "act-createSwimlane": "created swimlane __swimlane__ to board __board__", "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", "act-createCustomField": "created custom field __customField__ at board __board__", @@ -27,11 +27,11 @@ "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-createList": "added list __list__ to board __board__", "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedBoard": "Таблото __board__ е преместено в Архива.", "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", + "act-importBoard": "Импортирано е табло __board__", "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -64,20 +64,27 @@ "activity-unchecked-item": "размаркира %s от списък със задачи %s на %s", "activity-checklist-added": "добави списък със задачи към %s", "activity-checklist-removed": "премахна списък със задачи от %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "завършен списък със задачи %s на %s", "activity-checklist-uncompleted": "\"отзавърши\" чеклистта %s в %s", "activity-checklist-item-added": "добави точка към '%s' в/във %s", "activity-checklist-item-removed": "премахна точка от '%s' в %s", "add": "Добави", "activity-checked-item-card": "отбеляза %s в чеклист %s", "activity-unchecked-item-card": "размаркира %s в чеклист %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-completed-card": "изпълнен е списък със задачи __checklist__ към карта __card__ в списък __list__ в лента __swimlane__ към дъска __board__", "activity-checklist-uncompleted-card": "\"отзавърши\" чеклистта %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-editComment": "редактиран коментар %s", + "activity-deleteComment": "изтрит коментар %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Добави прикачен файл", "add-board": "Добави Табло", + "add-template": "Add Template", "add-card": "Добави карта", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Добави коридор", "add-subtask": "Добави подзадача", "add-checklist": "Добави списък със задачи", @@ -91,7 +98,7 @@ "admin": "Администратор", "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Съобщение", - "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-active": "Активно системно обявление", "admin-announcement-title": "Съобщение от администратора", "all-boards": "Всички табла", "and-n-other-card": "И __count__ друга карта", @@ -111,8 +118,10 @@ "restore-board": "Възстанови Таблото", "no-archived-boards": "Няма Табла в Архива.", "archives": "Архив", - "template": "Template", - "templates": "Templates", + "template": "Шаблон", + "templates": "Шаблони", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Възложи на член от екипа", "attached": "прикачен", "attachment": "Прикаченн файл", @@ -120,27 +129,29 @@ "attachmentDeletePopup-title": "Желаете ли да изтриете прикачения файл?", "attachments": "Прикачени файлове", "auto-watch": "Автоматично наблюдаване на таблата, когато са създадени", - "avatar-too-big": "Аватарът е прекалено голям (максимум 70KB)", + "avatar-too-big": "Аватарът е прекалено голям (максимум 520KB)", "back": "Назад", "board-change-color": "Промени цвета", "board-nb-stars": "%s звезди", "board-not-found": "Таблото не е намерено", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "boardChangeColorPopup-title": "Change Board Background", + "board-private-info": "Това табло ще бъде <strong>лично</strong>", + "board-public-info": "Това табло ще бъде <strong>публично</strong>", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Променете фона на таблото", "boardChangeTitlePopup-title": "Промени името на Таблото", - "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeVisibilityPopup-title": "Променете видимостта", "boardChangeWatchPopup-title": "Промени наблюдаването", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardMenuPopup-title": "Настройки на таблото", + "boardChangeViewPopup-title": "Изглед на таблото", "boards": "Табла", - "board-view": "Board View", + "board-view": "Изглед на таблото", "board-view-cal": "Календар", "board-view-swimlanes": "Коридори", - "board-view-collapse": "Collapse", + "board-view-collapse": "Събери", + "board-view-gantt": "План", "board-view-lists": "Списъци", "bucket-example": "Like “Bucket List” for example", - "cancel": "Cancel", + "cancel": "Отмени", "card-archived": "Тази карта е преместена в Архива.", "board-archived": "Това табло е преместено в Архива.", "card-comments-title": "Тази карта има %s коментар.", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Прикачи от", "cardCustomField-datePopup-title": "Промени датата", "cardCustomFieldsPopup-title": "Промени собствените полета", + "cardStartVotingPopup-title": "Започнете гласуване", + "positiveVoteMembersPopup-title": "Привърженици", + "negativeVoteMembersPopup-title": "Противници", + "card-edit-voting": "Промени гласуването", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "за", + "vote-against": "против", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Желаете да изтриете картата?", "cardDetailsActionsPopup-title": "Опции", "cardLabelsPopup-title": "Етикети", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Карти", "cards-count": "Карти", + "cards-count-one": "Карта", "casSignIn": "Sign In with CAS", "cardType-card": "Карта", "cardType-linkedCard": "Свързана карта", @@ -191,40 +236,41 @@ "close": "Затвори", "close-board": "Затвори Таблото", "close-board-pop": "Ще можете да възстановите Таблото като натиснете на бутона \"Архив\" в началото на хедъра.", + "close-card": "Close Card", "color-black": "черно", "color-blue": "синьо", "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-darkgreen": "тъмно зелено", + "color-gold": "златно", + "color-gray": "сиво", "color-green": "зелено", - "color-indigo": "indigo", + "color-indigo": "индиго", "color-lime": "лайм", "color-magenta": "magenta", "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-navy": "моряшко синьо", "color-orange": "оранжево", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "светло тюркоазено", + "color-peachpuff": "прасковено", "color-pink": "розово", - "color-plum": "plum", + "color-plum": "слива", "color-purple": "пурпурно", "color-red": "червено", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "кожено кафяво", + "color-silver": "сребристо", "color-sky": "светло синьо", "color-slateblue": "slateblue", "color-white": "бяло", "color-yellow": "жълто", - "unset-color": "Unset", + "unset-color": "Премахни", "comment": "Коментирай", "comment-placeholder": "Напиши коментар", "comment-only": "Само коментар", "comment-only-desc": "Може да коментира само в карти.", "no-comments": "Няма коментари", - "no-comments-desc": "Can not see comments and activities.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "no-comments-desc": "Не може да вижда коментари и активност", + "worker": "Работник", + "worker-desc": "Може само да премества карти, да ги добавя към себе си и да коментира", "computer": "Компютър", "confirm-subtask-delete-dialog": "Сигурен ли сте, че искате да изтриете подзадачата?", "confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?", @@ -244,12 +290,14 @@ "current": "сегашен", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Чекбокс", + "custom-field-currency": "Валута", + "custom-field-currency-option": "Код на валутата", "custom-field-date": "Дата", "custom-field-dropdown": "Падащо меню", "custom-field-dropdown-none": "(няма)", "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", + "custom-field-dropdown-options-placeholder": "Натиснете enter за добавяне на още опции", + "custom-field-dropdown-unknown": "(неизвестно)", "custom-field-number": "Номер", "custom-field-text": "Текст", "custom-fields": "Собствени полета", @@ -297,14 +345,28 @@ "error-board-notAMember": "За да направите това трябва да сте член на това табло", "error-json-malformed": "Текстът Ви не е валиден JSON", "error-json-schema": "JSON информацията Ви не съдържа информация във валиден формат", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Този списък не съществува", "error-user-doesNotExist": "Този потребител не съществува", "error-user-notAllowSelf": "Не можете да поканите себе си", "error-user-notCreated": "Този потребител не е създаден", "error-username-taken": "Това потребителско име е вече заето", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Имейлът е вече зает", "export-board": "Експортиране на Табло", - "sort": "Sort", + "export-board-json": "Експортирай таблото като JSON", + "export-board-csv": "Експортирай таблото като CSV", + "export-board-tsv": "Експортирай таблото като TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Експортиране на Табло", + "exportCardPopup-title": "Export card", + "sort": "Сортирай", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", "list-label-modifiedAt": "Last Access Time", @@ -314,17 +376,29 @@ "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Филтър", - "filter-cards": "Filter Cards or Lists", + "filter-cards": "Филтрирай картите или списъците", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Премахване на филтрите", + "filter-labels-label": "Филтрирай по етикет", "filter-no-label": "без етикет", + "filter-member-label": "Filter by member", "filter-no-member": "без член", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "Не е зададен изпълнител", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "Няма Собствени полета", "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", + "filter-hide-empty": "Скрий празните списъци", "filter-on": "Има приложени филтри", "filter-on-desc": "В момента филтрирате картите в това табло. Моля, натиснете тук, за да промените филтъра.", "filter-to-selection": "Филтрирай избраните", + "other-filters-label": "Други филтри", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Име", @@ -333,24 +407,28 @@ "headerBarCreateBoardPopup-title": "Създай Табло", "home": "Начало", "import": "Импорт", + "impersonate-user": "Impersonate user", "link": "Връзка", "import-board": "Импортирай Табло", "import-board-c": "Импортирай Табло", "import-board-title-trello": "Импорт на табло от Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Импортирането ще изтрие всичката налична информация в таблото и ще я замени с нова.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "От Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Копирайте валидната Ви JSON информация тук", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", + "importMapMembersAddPopup-title": "Избери член", "info": "Версия", "initials": "Инициали", "invalid-date": "Невалидна дата", @@ -365,19 +443,23 @@ "labels": "Етикети", "language": "Език", "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", + "leave-board": "Напуснете таблото", "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", + "leaveBoardPopup-title": "Желаете ли да напуснете таблото?", "link-card": "Връзка към тази карта", "list-archive-cards": "Премести всички карти от този списък в Архива", "list-archive-cards-pop": "Това ще премахне всички карти от този Списък от Таблото. За да видите картите в Архива и да ги върнете натиснете на \"Меню\" > \"Архив\".", "list-move-cards": "Премести всички карти в този списък", "list-select-cards": "Избери всички карти в този списък", - "set-color-list": "Set Color", + "set-color-list": "Изберете цвят", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "Потребителски настройки", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Импорт на карта от Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Още", "link-list": "Връзка към този списък", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -390,40 +472,42 @@ "memberMenuPopup-title": "Настройки на профила", "members": "Членове", "menu": "Меню", - "move-selection": "Move selection", + "move-selection": "Премести избраното", "moveCardPopup-title": "Премести картата", "moveCardToBottom-title": "Премести в края", "moveCardToTop-title": "Премести в началото", - "moveSelectionPopup-title": "Move selection", + "moveSelectionPopup-title": "Премести избраното", "multi-selection": "Множествен избор", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Множественият избор е приложен", - "muted": "Muted", + "muted": "Заглушено", "muted-info": "You will never be notified of any changes in this board", "my-boards": "Моите табла", "name": "Име", "no-archived-cards": "Няма карти в Архива.", "no-archived-lists": "Няма списъци в Архива.", "no-archived-swimlanes": "Няма коридори в Архива.", - "no-results": "No results", - "normal": "Normal", + "no-results": "Няма резултати", + "normal": "Нормално", "normal-desc": "Can view and edit cards. Can't change settings.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Получавате информация за всички карти, в които сте отбелязани или сте създали", "notify-watch": "Получавате информация за всички табла, списъци и карти, които наблюдавате", - "optional": "optional", + "optional": "не е задължително", "or": "или", "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", + "page-not-found": "Страницата не е намерена", "password": "Парола", "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", + "preview": "Преглед", + "previewAttachedImagePopup-title": "Преглед", + "previewClipboardImagePopup-title": "Преглед", + "private": "Лично", "private-desc": "This board is private. Only people added to the board can view and edit it.", "profile": "Профил", - "public": "Public", + "public": "Публично", "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", "quick-access-description": "Star a board to add a shortcut in this bar.", "remove-cover": "Remove Cover", @@ -433,16 +517,17 @@ "remove-member": "Премахни член", "remove-member-from-card": "Премахни от картата", "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", + "removeMemberPopup-title": "Желаете ли да премахнете членът?", + "rename": "Преименуване", "rename-board": "Промени името на Таблото", "restore": "Възстанови", "save": "Запази", "search": "Търсене", "rules": "Правила", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Въведете каквото търсите и натиснете Enter", "select-color": "Избери цвят", + "select-board": "Изберете табло", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Въведи WIP лимит", "shortcut-assign-self": "Добави себе си към тази карта", @@ -453,16 +538,17 @@ "shortcut-filter-my-cards": "Филтрирай моите карти", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Отвори/затвори сайдбара с филтри", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Покажи бройката на картите, ако списъка съдържа повече от", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", + "sidebar-open": "Отворете страничната лента", + "sidebar-close": "Затворете страничната лента", + "signupPopup-title": "Създайте акаунт", "star-board-title": "Click to star this board. It will show up at top of your boards list.", "starred-boards": "Любими табла", "starred-boards-description": "Любимите табла се показват в началото на списъка Ви.", - "subscribe": "Subscribe", - "team": "Team", + "subscribe": "Абонирайте се", + "team": "Екип", "this-board": "това табло", "this-card": "картата", "spent-time-hours": "Изработено време (часа)", @@ -471,29 +557,37 @@ "has-overtime-cards": "Има карти с оувъртайм", "has-spenttime-cards": "Има карти с изработено време", "time": "Време", - "title": "Title", + "title": "Заглавие", "tracking": "Следене", "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", + "type": "Тип", "unassign-member": "Unassign member", "unsaved-description": "You have an unsaved description.", "unwatch": "Спри наблюдаването", - "upload": "Upload", + "upload": "Качване", "upload-avatar": "Качване на аватар", "uploaded-avatar": "Качихте аватар", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Потребителско име", - "view-it": "View it", + "import-usernames": "Import Usernames", + "view-it": "Прегледайте", "warn-list-archived": "внимание: тази карта е в списък в Архива", "watch": "Наблюдавай", "watching": "Наблюдава", "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", + "welcome-board": "Табло за добре дошли ", "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", + "welcome-list1": "Основни", + "welcome-list2": "Напреднали", + "card-templates-swimlane": "Шаблони за карти", + "list-templates-swimlane": "Шаблони за списъци", + "board-templates-swimlane": "Шаблони за табло", "what-to-do": "What do you want to do?", "wipLimitErrorPopup-title": "Невалиден WIP лимит", "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", @@ -517,28 +611,28 @@ "smtp-tls": "TLS поддръжка", "send-from": "От", "send-smtp-test": "Изпрати тестов имейл на себе си", - "invitation-code": "Invitation Code", + "invitation-code": "Код от поканата", "email-invite-register-subject": "__inviter__ sent you an invitation", "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-subject": "Тестов SMTP e-mail", "email-smtp-test-text": "Успешно изпратихте имейл", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", + "error-invitation-code-not-exist": "Кода от покана не съществува", + "error-notAuthorized": "Нямате право да видите тази страница.", + "webhook-title": "Име на Webhook", + "webhook-token": "Жетон (незадължителен за идентификация)", + "outgoing-webhooks": "Изходящи Webhooks", + "bidirectional-webhooks": "Двупосочни Webhooks", + "outgoingWebhooksPopup-title": "Изходящи Webhooks", + "boardCardTitlePopup-title": "Филтър на Име на Карта", + "disable-webhook": "Изключи този Webhook", + "global-webhook": "Глобални Webhooks", + "new-outgoing-webhook": "Нов Изходящ Webhook", + "no-name": "(Непознат)", "Node_version": "Версия на Node", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "Meteor_version": "Версия на Meteor", + "MongoDB_version": "Версия на MongoDB", + "MongoDB_storage_engine": "MongoDB енджин за данни", + "MongoDB_Oplog_enabled": "MongoDB Oplog включен", "OS_Arch": "Архитектура на ОС", "OS_Cpus": "Брой CPU ядра", "OS_Freemem": "Свободна памет", @@ -553,14 +647,16 @@ "minutes": "минути", "seconds": "секунди", "show-field-on-card": "Покажи това поле в картата", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Покажи име на поле на мини-картата", "yes": "Да", "no": "Не", "accounts": "Профили", "accounts-allowEmailChange": "Разреши промяна на имейла", - "accounts-allowUserNameChange": "Allow Username Change", + "accounts-allowUserNameChange": "Позволи смяна на потребителско име", "createdAt": "Създаден на", + "modifiedAt": "Modified at", "verified": "Потвърден", "active": "Активен", "card-received": "Получена", @@ -569,33 +665,34 @@ "card-end-on": "Завършена на", "editCardReceivedDatePopup-title": "Промени датата на получаване", "editCardEndDatePopup-title": "Промени датата на завършване", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", + "setCardColorPopup-title": "Задай цвят", + "setCardActionsColorPopup-title": "Избери цвят", + "setSwimlaneColorPopup-title": "Избери цвят", + "setListColorPopup-title": "Избери цвят", "assigned-by": "Разпределена от", "requested-by": "Поискан от", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Изтриването е перманентно. Ще загубите всички списъци, карти и действия асоциирани с тази дъска.", + "delete-board-confirm-popup": "Всички списъци, карти, имена и действия ще бъдат изтрити и няма да можете да възстановите съдържанието на дъската. Няма връщане назад.", "boardDeletePopup-title": "Изтриване на Таблото?", "delete-board": "Изтрий таблото", "default-subtasks-board": "Подзадачи за табло __board__", "default": "по подразбиране", "queue": "Опашка", "subtask-settings": "Настройки на Подзадачите", - "card-settings": "Card Settings", + "card-settings": "Настройки на Карта", "boardSubtaskSettingsPopup-title": "Настройки за Подзадачите за това Табло", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", + "boardCardSettingsPopup-title": "Настройки на Карта", + "deposit-subtasks-board": "Вложете под-задачи към тази дъска:", "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", + "show-parent-in-minicard": "Покажи източника в мини-картата:", + "prefix-with-full-path": "Представка с пълен път", + "prefix-with-parent": "Представка с източник", + "subtext-with-full-path": "Под-текст с пълен път", + "subtext-with-parent": "Под-текст с източник", "change-card-parent": "Промени източника на картата", "parent-card": "Карта-източник", - "source-board": "Source board", + "source-board": "Дъска-източник", "no-parent": "Не показвай източника", "activity-added-label": "добави етикет '%s' към %s", "activity-removed-label": "премахна етикет '%s' от %s", @@ -614,34 +711,37 @@ "r-delete-rule": "Изтрий правилото", "r-new-rule-name": "Заглавие за новото правило", "r-no-rules": "Няма правила", + "r-trigger": "Добави спусък", + "r-action": "Действие", "r-when-a-card": "Когато карта", "r-is": "е", "r-is-moved": "преместена", - "r-added-to": "добавена в", + "r-added-to": "Добавено към", "r-removed-from": "премахната от", "r-the-board": "таблото", "r-list": "списък", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", + "list": "Списък", + "set-filter": "Задай филтър", + "r-moved-to": "Преместено в", + "r-moved-from": "Преместено от", "r-archived": "Преместено в Архива", "r-unarchived": "Възстановено от Архива", "r-a-card": "карта", "r-when-a-label-is": "When a label is", "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", + "r-list-name": "име на списъка", + "r-when-a-member": "Когато някой член ", + "r-when-the-member": "Когато членът", "r-name": "име", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", + "r-when-a-attach": "Когато някой прикачен файл", + "r-when-a-checklist": "Когато някой списък със задачи", + "r-when-the-checklist": "Когато списъкът със задачи", + "r-completed": "Завършено", + "r-made-incomplete": "е зададен като незавършен", + "r-when-a-item": "Когато някоя задача", + "r-when-the-item": "Когато задачата", + "r-checked": "Готово", + "r-unchecked": "Неготово", "r-move-card-to": "Премести картата в", "r-top-of": "началото на", "r-bottom-of": "края на", @@ -650,41 +750,42 @@ "r-unarchive": "Възстанови от Архива", "r-card": "карта", "r-add": "Добави", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", + "r-remove": "Премахване", + "r-label": "етикет", + "r-member": "член", + "r-remove-all": "Премахнете всички членове от картата", + "r-set-color": "Задай следния цвят: ", + "r-checklist": "списък със задачи", + "r-check-all": "Маркирай всички като завършени", + "r-uncheck-all": "Отмаркирай всички като незавършени", "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", + "r-check": "Маркирай като готово", + "r-uncheck": "Маркирай като незавършено", + "r-item": "елемент", "r-of-checklist": "of checklist", "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", + "r-to": "до", + "r-of": "of", + "r-subject": "Тема", "r-rule-details": "Детайли за правилото", "r-d-move-to-top-gen": "Move card to top of its list", "r-d-move-to-top-spec": "Move card to top of list", "r-d-move-to-bottom-gen": "Move card to bottom of its list", "r-d-move-to-bottom-spec": "Move card to bottom of list", "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", + "r-d-send-email-to": "до", + "r-d-send-email-subject": "Тема", + "r-d-send-email-message": "Съобщение", "r-d-archive": "Премести картата в Архива", "r-d-unarchive": "Възстанови картата от Архива", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", + "r-d-add-label": "Добави етикет", + "r-d-remove-label": "Премахни етикет", + "r-create-card": "Създай нова карта", "r-in-list": "in list", "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", + "r-d-add-member": "Добави член", + "r-d-remove-member": "Премахни член", + "r-d-remove-all-member": "Премахни всички членове", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", "r-d-check-one": "Check item", @@ -692,7 +793,7 @@ "r-d-check-of-list": "of checklist", "r-d-add-checklist": "Добави чеклист", "r-d-remove-checklist": "Премахни чеклист", - "r-by": "by", + "r-by": "от", "r-add-checklist": "Добави чеклист", "r-with-items": "с точки", "r-items-list": "точка1,точка2,точка3", @@ -702,12 +803,12 @@ "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "When a card is moved to another list", "r-set": "Set", - "r-update": "Update", + "r-update": "Обнови", "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", + "r-df-start-at": "начало", + "r-df-due-at": "срок", + "r-df-end-at": "край", + "r-df-received-at": "получено", "r-to-current-datetime": "to current date/time", "r-remove-value-from": "Remove value from", "ldap": "LDAP", @@ -716,22 +817,24 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "layout": "Layout", - "hide-logo": "Hide Logo", + "layout": "Изглед", + "hide-logo": "Скрий логото", "add-custom-html-after-body-start": "Add Custom HTML after <body> start", "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", + "error-undefined": "Нещо се обърка", "error-ldap-login": "An error occurred while trying to login", "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", + "duplicate-board": "Копирай таблото", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", + "restore-all": "Възстанови всичко", + "delete-all": "Изтрий всичко", "loading": "Loading, please wait.", - "previous_as": "last time was", + "previous_as": "последният път беше", "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -757,13 +862,201 @@ "cardAssigneesPopup-title": "Assignee", "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", - "new": "New", + "new": "Нов", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "Нов екип", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", - "notifications": "Notifications", + "notifications": "Уведомления", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Понеделник", + "tuesday": "Вторник", + "wednesday": "Сряда", + "thursday": "Четвъртък", + "friday": "Петък", + "saturday": "Събота", + "sunday": "Неделя", + "status": "Състояние", + "swimlane": "Swimlane", + "owner": "Притежател", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Задача", + "create-task": "Добави задача", + "ok": "Окей", + "organizations": "Организации", + "teams": "Екипи", + "displayName": "Име", + "shortName": "Кратко име", + "website": "Уебсайт", + "person": "Person", + "my-cards": "My Cards", + "card": "Карта", + "board": "Табло", + "context-separator": " / ", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "табло", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "списък", + "operator-list-abbrev": "l", + "operator-label": "етикет", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "член", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "състояние", + "operator-due": "срок", + "operator-created": "създаден", + "operator-modified": "modified", + "operator-sort": "подреди", + "operator-comment": "коментар", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "срок", + "predicate-modified": "modified", + "predicate-created": "създаден", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "списък със задачи", + "predicate-start": "начало", + "predicate-end": "край", + "predicate-assignee": "assignee", + "predicate-member": "член", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Номер", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/br.i18n.json b/i18n/br.i18n.json index 645988da6..a3ed035b4 100644 --- a/i18n/br.i18n.json +++ b/i18n/br.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Kemmañ al liv", "board-nb-stars": "%s stered", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Diverkañ ar gartenn ?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Kartennoù", "cards-count": "Kartennoù", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "du", "color-blue": "glas", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Muioc’h", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index 8890a16eb..d171d017f 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Afegeix adjunt", "add-board": "Afegeix Tauler", + "add-template": "Add Template", "add-card": "Afegeix Fitxa", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Afegeix carril de natació", "add-subtask": "Afegir Subtasca", "add-checklist": "Afegeix checklist", @@ -113,6 +120,8 @@ "archives": "Desa", "template": "Plantilla", "templates": "Plantilles", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assignar membre", "attached": "adjuntat", "attachment": "Adjunt", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Esborrar adjunt?", "attachments": "Adjunts", "auto-watch": "Segueix automàticament el taulers quan són creats", - "avatar-too-big": "L'avatar es massa gran (70KM max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Enrere", "board-change-color": "Canvia el color", "board-nb-stars": "%s estrelles", "board-not-found": "No s'ha trobat el tauler", "board-private-info": "Aquest tauler serà <strong> privat.", "board-public-info": "Aquest tauler serà <strong> públic.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Canvia fons del tauler", "boardChangeTitlePopup-title": "Canvia el nom tauler", "boardChangeVisibilityPopup-title": "Canvia visibilitat", @@ -138,6 +148,7 @@ "board-view-cal": "Calendari", "board-view-swimlanes": "Carrils de Natació", "board-view-collapse": "Contraure", + "board-view-gantt": "Gantt", "board-view-lists": "Llistes", "bucket-example": "Igual que “Bucket List”, per exemple", "cancel": "Cancel·la", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Adjunta des de", "cardCustomField-datePopup-title": "Canviar data", "cardCustomFieldsPopup-title": "Editar camps personalitzats", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Esborrar fitxa?", "cardDetailsActionsPopup-title": "Accions de fitxes", "cardLabelsPopup-title": "Etiquetes", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Fitxes", "cards-count": "Fitxes", + "cards-count-one": "Fitxa", "casSignIn": "Sign In with CAS", "cardType-card": "Fitxa", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Tanca", "close-board": "Tanca tauler", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "negre", "color-blue": "blau", "color-crimson": "carmesí", @@ -244,6 +290,8 @@ "current": "Actual", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Data", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Necessites ser membre d'aquest tauler per dur a terme aquesta acció", "error-json-malformed": "El text no és JSON vàlid", "error-json-schema": "La dades JSON no contenen la informació en el format correcte", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "La llista no existeix", "error-user-doesNotExist": "L'usuari no existeix", "error-user-notAllowSelf": "No et pots convidar a tu mateix", "error-user-notCreated": "L'usuari no s'ha creat", "error-username-taken": "Aquest usuari ja existeix", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "L'adreça de correu electrònic ja és en ús", "export-board": "Exporta tauler", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exporta tauler", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtre", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Elimina filtre", + "filter-labels-label": "Filter by label", "filter-no-label": "Sense etiqueta", + "filter-member-label": "Filter by member", "filter-no-member": "Sense membres", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filtra per", "filter-on-desc": "Estau filtrant fitxes en aquest tauler. Feu clic aquí per editar el filtre.", "filter-to-selection": "Filtra selecció", + "other-filters-label": "Other Filters", "advanced-filter-label": "Filtre avançat", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nom complet", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Crea tauler", "home": "Inici", "import": "importa", + "impersonate-user": "Impersonate user", "link": "Enllaç", "import-board": "Importa tauler", "import-board-c": "Importa tauler", "import-board-title-trello": "Importa tauler des de Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Estau segur que voleu esborrar aquesta checklist?", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Des de Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Aferra codi JSON vàlid aquí", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Mapeja el membres", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Revisa l'assignació de membres", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Selecciona un usuari", @@ -375,9 +453,13 @@ "list-select-cards": "Selecciona totes les fitxes d'aquesta llista", "set-color-list": "Set Color", "listActionPopup-title": "Accions de la llista", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Accions de Carril de Natació", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "importa una fitxa de Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Més", "link-list": "Enllaça a aquesta llista", "list-delete-pop": "Totes les accions seran esborrades de la llista d'activitats i no serà possible recuperar la llista", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Mou a la part superior", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selecció", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selecció està activada", "muted": "En silenci", "muted-info": "No seràs notificat dels canvis en aquest tauler", @@ -441,8 +525,9 @@ "search": "Cerca", "rules": "Regles", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text que cercar?", + "search-example": "Write text you search and press Enter", "select-color": "Selecciona color", + "select-board": "Select Board", "set-wip-limit-value": "Limita el màxim nombre de tasques en aquesta llista", "setWipLimitPopup-title": "Configura el Límit de Treball en Progrès", "shortcut-assign-self": "Assigna't la ftixa actual", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtra les meves fitxes", "shortcut-show-shortcuts": "Mostra aquesta lista d'accessos directes", "shortcut-toggle-filterbar": "Canvia la barra lateral del tauler", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Canvia Sidebar del Tauler", "show-cards-minimum-count": "Mostra contador de fitxes si la llista en conté més de", "sidebar-open": "Mostra barra lateral", @@ -481,7 +567,15 @@ "upload": "Puja", "upload-avatar": "Actualitza avatar", "uploaded-avatar": "Avatar actualitzat", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Nom d'Usuari", + "import-usernames": "Import Usernames", "view-it": "Vist", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Observa", @@ -553,7 +647,8 @@ "minutes": "minuts", "seconds": "segons", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Si", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Permet modificar correu electrònic", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Creat ", + "modifiedAt": "Modified at", "verified": "Verificat", "active": "Actiu", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assignat Per", "requested-by": "Demanat Per", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No hi han regles", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Afegiu una descripció més detallada", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Fitxa", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/cs.i18n.json b/i18n/cs.i18n.json index 5a5ce30fe..f3f8b1e4d 100644 --- a/i18n/cs.i18n.json +++ b/i18n/cs.i18n.json @@ -1,6 +1,6 @@ { "accept": "Přijmout", - "act-activity-notify": "Notifikace aktivit", + "act-activity-notify": "Oznámení", "act-addAttachment": "přidal(a) přílohu __attachment__ na kartu __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-deleteAttachment": "smazal(a) přílohu __attachment__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-addSubtask": "přidal(a) podúkol __subtask__ na kartu __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", @@ -17,14 +17,14 @@ "act-completeChecklist": "dokončil(a) zaškrtávací seznam __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-uncompleteChecklist": "zrušil(a) dokončení zaškrtávacího seznamu __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-addComment": "přidal(a) komentář na kartě __card__: __comment__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "editoval(a) komentář na kartě __card__:__comment__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", + "act-deleteComment": "smazal(a) komentář na kartě __card__:__comment__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-createBoard": "přidal(a) tablo __board__", "act-createSwimlane": "created swimlane __swimlane__ to board __board__", "act-createCard": "přidal(a) kartu __card__ do sloupce __list__ ve swimlane __swimlane__ na tablu __board__", "act-createCustomField": "přidal(a) pole __customField__ na tablo __board__", "act-deleteCustomField": "odebral(a) pole __customField__ na tablu __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-setCustomField": "editoval(a) pole __customField__:__customFieldValue__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "act-createList": "přidal(a) sloupec __list__ do tabla __board__", "act-addBoardMember": "přidal(a) člena __member__ do tabla __board__", "act-archivedBoard": "Tablo __board__ přesunuto do Archivu", @@ -64,7 +64,7 @@ "activity-unchecked-item": "nedokončen %s v seznamu %s z %s", "activity-checklist-added": "přidán checklist do %s", "activity-checklist-removed": "odstraněn checklist z %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "dokončen %s zaškrtávací seznam z %s", "activity-checklist-uncompleted": "nedokončen seznam %s z %s", "activity-checklist-item-added": "přidána položka checklist do '%s' v %s", "activity-checklist-item-removed": "odstraněna položka seznamu do '%s' v %s", @@ -73,11 +73,18 @@ "activity-unchecked-item-card": "nedokončen %s v seznamu %s", "activity-checklist-completed-card": "dokončil(a) zaškrtávací seznam __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__", "activity-checklist-uncompleted-card": "nedokončený seznam %s", - "activity-editComment": "edited comment %s", + "activity-editComment": "upravit komentář %s", "activity-deleteComment": "smazat komentář %s", + "activity-receivedDate": "editoval(a) datum přijetí na %s z %s", + "activity-startDate": "editoval(a) datum zahájení na %s z %s", + "activity-dueDate": "editoval(a) termín dokončení na %s z %s", + "activity-endDate": "editoval(a) datum ukončení na %s z %s", "add-attachment": "Přidat přílohu", "add-board": "Přidat tablo", + "add-template": "Přidat šablonu", "add-card": "Přidat kartu", + "add-card-to-top-of-list": "Přidat kartu na začátek seznamu", + "add-card-to-bottom-of-list": "Přidat kartu na konec seznamu", "add-swimlane": "Přidat Swimlane", "add-subtask": "Přidat Podúkol", "add-checklist": "Přidat zaškrtávací seznam", @@ -113,6 +120,8 @@ "archives": "Archiv", "template": "Šablona", "templates": "Šablony", + "template-container": "Kontejner šablony", + "add-template-container": "Přidat kontejner šablony", "assign-member": "Přiřadit člena", "attached": "přiloženo", "attachment": "Příloha", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Smazat přílohu?", "attachments": "Přílohy", "auto-watch": "Automaticky sleduj tabla když jsou vytvořena", - "avatar-too-big": "Avatar obrázek je příliš velký (70KB max)", + "avatar-too-big": "Avatar je příliš velký (520KB max)", "back": "Zpět", "board-change-color": "Změnit barvu", "board-nb-stars": "%s hvězdiček", "board-not-found": "Tablo nenalezeno", "board-private-info": "Toto tablo bude <strong>soukromé</strong>.", "board-public-info": "Toto tablo bude <strong>veřejné</strong>.", + "board-drag-drop-reorder-or-click-open": "Přetažením změníte pořadí ikon tabel. Kliknutím otevřete tablo.", "boardChangeColorPopup-title": "Změnit pozadí tabla", "boardChangeTitlePopup-title": "Přejmenovat tablo", "boardChangeVisibilityPopup-title": "Upravit viditelnost", @@ -138,6 +148,7 @@ "board-view-cal": "Kalendář", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Sbalit", + "board-view-gantt": "Gannt", "board-view-lists": "Sloupce", "bucket-example": "Například \"O čem sním\"", "cancel": "Zrušit", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Přiložit formulář", "cardCustomField-datePopup-title": "Změnit datum", "cardCustomFieldsPopup-title": "Upravit vlastní pole", + "cardStartVotingPopup-title": "Začít hlasování", + "positiveVoteMembersPopup-title": "Navrhovatelé", + "negativeVoteMembersPopup-title": "Oponenti", + "card-edit-voting": "Úprava hlasování", + "editVoteEndDatePopup-title": "Změnit datum konce hlasování", + "allowNonBoardMembers": "Povolit všechny přihlášené uživatele", + "vote-question": "Otázka hlasování", + "vote-public": "Ukázat kdo jak hlasoval", + "vote-for-it": "pro", + "vote-against": "proti", + "deleteVotePopup-title": "Smazat hlas?", + "vote-delete-pop": "Smazání je nevratné. Ztratíte vše spojené s tímto hlasováním.", + "cardStartPlanningPokerPopup-title": "Zahájit plánovací poker", + "card-edit-planning-poker": "Upravit plánovací poker", + "editPokerEndDatePopup-title": "Změnit datum konce plánovacího pokeru", + "poker-question": "Plánovací poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Hotovo", + "poker-result-votes": "Hlasů", + "poker-result-who": "Kdo", + "poker-replay": "Zopakovat", + "set-estimation": "Nastavit odhad", + "deletePokerPopup-title": "Smazat plánovací poker?", + "poker-delete-pop": "Smazání je trvalé. Přijdete o všechny akce asociované s tímto plánovacím pokerem.", "cardDeletePopup-title": "Smazat kartu?", "cardDetailsActionsPopup-title": "Akce karty", "cardLabelsPopup-title": "Štítky", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Vytvořit šablonu", "cards": "Karty", "cards-count": "Karty", + "cards-count-one": "Karta", "casSignIn": "Přihlásit pomocí CAS", "cardType-card": "Karta", "cardType-linkedCard": "Propojená karta", @@ -191,6 +236,7 @@ "close": "Zavřít", "close-board": "Zavřít tablo", "close-board-pop": "Budete moci obnovit tablo kliknutím na tlačítko \"Archiv\" v hlavním menu.", + "close-card": "Zavřít kartu", "color-black": "černá", "color-blue": "modrá", "color-crimson": "karmínová", @@ -223,8 +269,8 @@ "comment-only-desc": "Může přidávat komentáře pouze do karet.", "no-comments": "Žádné komentáře", "no-comments-desc": "Nemůže vidět komentáře a aktivity", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker": "Pracovník", + "worker-desc": "Je možné pouze přesouvat karty, přiřazovat ke kartám a komentovat.", "computer": "Počítač", "confirm-subtask-delete-dialog": "Opravdu chcete smazat tento podúkol?", "confirm-checklist-delete-dialog": "Opravdu chcete smazat tento checklist?", @@ -234,7 +280,7 @@ "copyCardPopup-title": "Kopírovat kartu", "copyChecklistToManyCardsPopup-title": "Kopírovat checklist do více karet", "copyChecklistToManyCardsPopup-instructions": "Názvy a popisy cílové karty v tomto formátu JSON", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Nadpis první karty\", \"description\":\"Popis druhé karty\"}, {\"title\":\"Nadpis druhé karty\",\"description\":\"Popis druhé karty\"},{\"title\":\"Nadpis poslední kary\",\"description\":\"Popis poslední karty\"} ]", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Nadpis první karty\", \"description\":\"Popis první karty\"}, {\"title\":\"Nadpis druhé karty\",\"description\":\"Popis druhé karty\"},{\"title\":\"Nadpis poslední kary\",\"description\":\"Popis poslední karty\"} ]", "create": "Vytvořit", "createBoardPopup-title": "Vytvořit tablo", "chooseBoardSourcePopup-title": "Importovat tablo", @@ -244,6 +290,8 @@ "current": "Aktuální", "custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Měna", + "custom-field-currency-option": "Kód měny", "custom-field-date": "Datum", "custom-field-dropdown": "Rozbalovací seznam", "custom-field-dropdown-none": "(prázdné)", @@ -297,34 +345,60 @@ "error-board-notAMember": "K provedení změny musíš být členem tohoto tabla", "error-json-malformed": "Tvůj text není validní JSON", "error-json-schema": "Tato JSON data neobsahují správné informace v platném formátu", + "error-csv-schema": "Vaše CSV (hodnoty oddělené čárkami) / TSV (hodnoty oddělené tabulátory) neobsahují ssprávné informace ve správném formátu", "error-list-doesNotExist": "Tento sloupec ;neexistuje", "error-user-doesNotExist": "Tento uživatel neexistuje", "error-user-notAllowSelf": "Nemůžeš pozvat sám sebe", "error-user-notCreated": "Tento uživatel není vytvořen", "error-username-taken": "Toto uživatelské jméno již existuje", + "error-orgname-taken": "Jméno organizace již existuje", + "error-teamname-taken": "Jméno týmu již existuje", "error-email-taken": "Tento email byl již použit", "export-board": "Exportovat tablo", + "export-board-json": "Exportovat tablo do JSON", + "export-board-csv": "Exportovat tablo do CSV", + "export-board-tsv": "Exportovat tablo do TSV", + "export-board-excel": "Exportovat tablo do Excelu", + "user-can-not-export-excel": "Uživatel nemůže exportovat do Excelu", + "export-board-html": "Exportovat tablo do HTML", + "export-card": "Exportovat kartu", + "export-card-pdf": "Exportovat kartu do PDF", + "user-can-not-export-card-to-pdf": "Uživatel nemůže exportovat kartu do PDF", + "exportBoardPopup-title": "Exportovat tablo", + "exportCardPopup-title": "Exportovat kartu", "sort": "řadit", - "sort-desc": "Click to Sort List", + "sort-desc": "Kliknout pro třídění seznamu", "list-sort-by": "řadit seznam podle", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "list-label-modifiedAt": "Čas posledního přístupu", + "list-label-title": "Název seznamu", + "list-label-sort": "Vaše ruční uspořádání", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Filtr", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", + "filter-cards": "Filtrovat karty nebo seznamy", + "filter-dates-label": "Filtrovat podle data", + "filter-no-due-date": "Bez termínu", + "filter-overdue": "po termínu", + "filter-due-today": "Termín dnes", + "filter-due-this-week": "Termín tento týden", + "filter-due-tomorrow": "Termín zítra", + "list-filter-label": "Filtrovat seznam podle názvu", "filter-clear": "Vyčistit filtr", + "filter-labels-label": "Filtrovat podle štítku", "filter-no-label": "Žádný štítek", + "filter-member-label": "Filtrovat podle člena", "filter-no-member": "Žádný člen", + "filter-assignee-label": "Filtrovat podle řešitele", + "filter-no-assignee": "Bez řešitele", + "filter-custom-fields-label": "Filtrovat podle polí", "filter-no-custom-fields": "Žádné vlastní pole", "filter-show-archive": "Zobrazit archivované listy", "filter-hide-empty": "Skrýt prázdné listy", "filter-on": "Filtr je zapnut", "filter-on-desc": "Filtrujete karty tohoto tabla. Pro úpravu filtru klikni sem.", "filter-to-selection": "Filtrovat výběr", + "other-filters-label": "Ostatní filtry", "advanced-filter-label": "Pokročilý filtr", "advanced-filter-description": "Pokročilý filtr dovoluje zapsat řetězec následujících operátorů: == != <= >= && || () Operátory jsou odděleny mezerou. Můžete filtrovat všechny vlastní pole zadáním jejich názvů nebo hodnot. Například: Pole1 == Hodnota1. Poznámka: Pokud pole nebo hodnoty obsahují mezery, je potřeba je obalit v jednoduchých uvozovkách. Například: 'Pole 1' == 'Hodnota 1'. Pro ignorovaní kontrolních znaků (' \\ /) je možné použít \\. Například Pole1 == I\\'m. Můžete také kombinovat více podmínek. Například P1 == H1 || P1 == H2. Obvykle jsou operátory interpretovány zleva doprava. Jejich pořadí můžete měnit pomocí závorek. Například: P1 == H1 && ( P2 == H2 || P2 == H3 )", "fullname": "Celé jméno", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Vytvořit tablo", "home": "Domů", "import": "Import", + "impersonate-user": "Napodobit uživatele", "link": "Propojit", "import-board": "Importovat tablo", "import-board-c": "Importovat tablo", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Importovat tablo z předchozího exportu", - "import-sandstorm-backup-warning": "Nemažte data, která importujete z původního exportovaného tabla nebo Trello předtím, nežli zkontrolujete, jestli lze tuto část zavřít a znovu otevřít nebo jestli se Vám nezobrazuje chyba tabla, což znamená ztrátu dat.", - "import-sandstorm-warning": "Importované tablo spaže všechny existující data v tablu a nahradí je importovaným tablem.", + "import-board-title-csv": "Importovat tablo z CSV/TSV", "from-trello": "Z Trella", "from-wekan": "Z předchozího exportu", + "from-csv": "Z CSV/TSV", "import-board-instruction-trello": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text", + "import-board-instruction-csv": "Vložit do vašeho Comma Separated Values (CSV)/ Tab Separated Values (TSV).", "import-board-instruction-wekan": "Ve vašem tablu jděte do 'Menu', klikněte na 'Exportovat tablo' a zkopírujte text ze staženého souboru.", "import-board-instruction-about-errors": "Někdy import funguje i přestože dostáváte chyby při importování tabla, které se zobrazí na stránce Všechna tabla.", "import-json-placeholder": "Sem vlož validní JSON data", + "import-csv-placeholder": "Zde vložte vaše platné CSV/TSV data", "import-map-members": "Mapovat členy", "import-members-map": "Toto importované tablo obsahuje několik osob. Prosím namapujte osoby z importu na místní uživatelské účty.", + "import-members-map-note": "Modifikováno", "import-show-user-mapping": "Zkontrolovat namapování členů", "import-user-select": "Vyberte existující uživatelský účet, kterého chcete použít pro tuto osobu", "importMapMembersAddPopup-title": "Zvolte osobu", @@ -375,9 +453,13 @@ "list-select-cards": "Vybrat všechny karty v tomto sloupci", "set-color-list": "Nastavit barvu", "listActionPopup-title": "Vypsat akce", + "settingsUserPopup-title": "Nastavení uživatele", + "settingsTeamPopup-title": "Nastavení týmu", + "settingsOrgPopup-title": "Nastavení organizace", "swimlaneActionPopup-title": "Akce swimlane", "swimlaneAddPopup-title": "Přidat swimlane dolů", "listImportCardPopup-title": "Importovat Trello kartu", + "listImportCardsTsvPopup-title": "Importovat Excel CSV/TSV", "listMorePopup-title": "Více", "link-list": "Odkaz na tento sloupec", "list-delete-pop": "Všechny akce budou odstraněny z kanálu aktivity a nebude možné sloupec obnovit. Toto nelze vrátit zpět.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Přesunout nahoru", "moveSelectionPopup-title": "Přesunout výběr", "multi-selection": "Multi-výběr", + "multi-selection-label": "Vyberte štítek", + "multi-selection-member": "Vyberte člena", "multi-selection-on": "Multi-výběr je zapnut", "muted": "Umlčeno", "muted-info": "Nikdy nedostanete oznámení o změně v tomto tablu.", @@ -440,9 +524,10 @@ "save": "Uložit", "search": "Hledat", "rules": "Pravidla", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Hledaný text", + "search-cards": "Hledání napříč názvy karet/seznamů, popisy a vlasními poli tohoto tabla", + "search-example": "Napište hledaný text a stiskněte Enter", "select-color": "Vybrat barvu", + "select-board": "Vybrat tablo", "set-wip-limit-value": "Nastaví limit pro maximální počet úkolů ve sloupci.", "setWipLimitPopup-title": "Nastavit WIP Limit", "shortcut-assign-self": "Přiřadit sebe k aktuální kartě", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtrovat mé karty", "shortcut-show-shortcuts": "Otevřít tento seznam odkazů", "shortcut-toggle-filterbar": "Přepnout lištu filtrování", + "shortcut-toggle-searchbar": "Přepnout lištu vyhledávání", "shortcut-toggle-sidebar": "Přepnout lištu tabla", "show-cards-minimum-count": "Zobrazit počet karet pokud sloupec obsahuje více než", "sidebar-open": "Otevřít boční panel", @@ -481,7 +567,15 @@ "upload": "Nahrát", "upload-avatar": "Nahrát avatar", "uploaded-avatar": "Avatar nahrán", + "custom-top-left-corner-logo-image-url": "URL obrázku vlastního loga v levém horním rohu", + "custom-top-left-corner-logo-link-url": "URL odkazu vlastního loga v levém horním rohu", + "custom-top-left-corner-logo-height": "Výška vlastního loga v horním levém rohu. Defaultně: 27", + "custom-login-logo-image-url": "URL vlastního loga přihlášení", + "custom-login-logo-link-url": "URL odkazu vlastního loga přihlášení", + "text-below-custom-login-logo": "Text pod vlastním logem přihlášení", + "automatic-linked-url-schemes": "Vlastní URL schémata, která by automaticky měla být klikatelná.\nJedno schéma na řádek", "username": "Uživatelské jméno", + "import-usernames": "Importovat uživatelská jména", "view-it": "Zobrazit", "warn-list-archived": "varování: tato karta je v seznamu v Archivu", "watch": "Sledovat", @@ -524,21 +618,21 @@ "email-smtp-test-text": "Email byl úspěšně odeslán", "error-invitation-code-not-exist": "Kód pozvánky neexistuje.", "error-notAuthorized": "Nejste autorizován k prohlížení této stránky.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", + "webhook-title": "Název webhooku", + "webhook-token": "Token (Volitelné pro autentizaci)", "outgoing-webhooks": "Odchozí Webhooky", - "bidirectional-webhooks": "Two-Way Webhooks", + "bidirectional-webhooks": "Dvousměrné webhooky", "outgoingWebhooksPopup-title": "Odchozí Webhooky", "boardCardTitlePopup-title": "Filtr názvů karet", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", + "disable-webhook": "Zakázat tento webhook", + "global-webhook": "Globální webhooks", "new-outgoing-webhook": "Nové odchozí Webhooky", "no-name": "(Neznámé)", "Node_version": "Node verze", - "Meteor_version": "Meteor version", + "Meteor_version": "Verze Meteor", "MongoDB_version": "MongoDB verze", "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "MongoDB_Oplog_enabled": "MongoDB Oplog povolen", "OS_Arch": "OS Architektura", "OS_Cpus": "OS Počet CPU", "OS_Freemem": "OS Volná paměť", @@ -553,7 +647,8 @@ "minutes": "minut", "seconds": "sekund", "show-field-on-card": "Ukázat toto pole na kartě", - "automatically-field-on-card": "Automaticky vytvořit pole na všech kartách", + "automatically-field-on-card": "Přidat pole na nové karty", + "always-field-on-card": "Přidat pole na všechny karty", "showLabel-field-on-card": "Ukázat štítek pole na minikartě", "yes": "Ano", "no": "Ne", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Povolit změnu Emailu", "accounts-allowUserNameChange": "Povolit změnu uživatelského jména", "createdAt": "Vytvořeno v", + "modifiedAt": "Modifikováno", "verified": "Ověřen", "active": "Aktivní", "card-received": "Přijato", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Vyber barvu", "assigned-by": "Přidělil(a)", "requested-by": "Vyžádal(a)", + "card-sorting-by-number": "Řazení karet podle čísla", "board-delete-notice": "Smazání je trvalé. Přijdete o všechny sloupce, karty a akce spojené s tímto tablem.", "delete-board-confirm-popup": "Všechny sloupce, štítky a aktivity budou smazány a obsah tabla nebude možné obnovit. Toto nelze vrátit zpět.", "boardDeletePopup-title": "Smazat tablo?", @@ -583,9 +680,9 @@ "default": "Výchozí", "queue": "Fronta", "subtask-settings": "Nastavení podúkolů", - "card-settings": "Card Settings", + "card-settings": "Nastavení karet", "boardSubtaskSettingsPopup-title": "Nastavení podúkolů tabla", - "boardCardSettingsPopup-title": "Card Settings", + "boardCardSettingsPopup-title": "Nastavení karet", "deposit-subtasks-board": "Vložit podúkoly do tohoto tabla", "deposit-subtasks-list": "Landing list for subtasks deposited here:", "show-parent-in-minicard": "Ukázat předka na minikartě", @@ -614,13 +711,16 @@ "r-delete-rule": "Smazat pravidlo", "r-new-rule-name": "Nový název pravidla", "r-no-rules": "Žádná pravidla", + "r-trigger": "Spouštěč", + "r-action": "Akce", "r-when-a-card": "Pokud karta", "r-is": "je", "r-is-moved": "je přesunuto", - "r-added-to": "přidáno do", + "r-added-to": "Přidáno", "r-removed-from": "Odstraněno z", "r-the-board": "tablo", "r-list": "sloupce", + "list": "Seznam", "set-filter": "Nastavit filtr", "r-moved-to": "Přesunuto do", "r-moved-from": "Přesunuto z", @@ -665,6 +765,7 @@ "r-of-checklist": "ze zaškrtávacího seznamu", "r-send-email": "Odeslat e-mail", "r-to": "komu", + "r-of": "z", "r-subject": "předmět", "r-rule-details": "Podrobnosti pravidla", "r-d-move-to-top-gen": "Přesunout kartu na začátek toho sloupce", @@ -701,15 +802,15 @@ "r-board-note": "Note: leave a field empty to match every possible value.", "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "Když je karta přesunuta do jiného sloupce", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", + "r-set": "Nastavit", + "r-update": "Aktualizovat", + "r-datefield": "pole datum", "r-df-start-at": "začátek", - "r-df-due-at": "due", + "r-df-due-at": "do", "r-df-end-at": "konec", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-df-received-at": "přijato", + "r-to-current-datetime": "stávající datum/čas", + "r-remove-value-from": "Odstranit hodnotu z", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", @@ -725,45 +826,237 @@ "display-authentication-method": "Zobraz způsob ověřování", "default-authentication-method": "Zobraz způsob ověřování", "duplicate-board": "Duplikovat tablo", + "org-number": "Počet organizací je:", + "team-number": "Počet týmů je:", "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlaneDeletePopup-title": "Smazat Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", "restore-all": "Restore all", "delete-all": "Delete all", "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "previous_as": "naposledy bylo", + "act-a-dueAt": "změnil(a) termín dokončení\nKdy: __timeValue__\nKde: __card__\n předchozí termín dokončení byl __timeOldValue__", + "act-a-endAt": "změnil(a) čas ukončení na __timeValue__ z (__timeOldValue__)", + "act-a-startAt": "změnil(a) čas zahájení na __timeValue__ z (__timeOldValue__)", + "act-a-receivedAt": "změnil(a) čas přijetí na __timeValue__ z (__timeOldValue__)", + "a-dueAt": "Změnil(a) požadovaný termín dokončení ", + "a-endAt": "změnil(a) čas ukončení", + "a-startAt": "změnil(a) čas zahájení", + "a-receivedAt": "změnil(a) čas přijetí", + "almostdue": "Stávající termín dokončení %s se blíží", + "pastdue": "Stávající termín dokončení %s je v minulosti", + "duenow": "Stávající termín dokončení %s je dnes", + "act-newDue": "__list__/__card__ má první připomínku termínu dokončení [__board__]", + "act-withDue": "__list__/__card__ připomínky termínu dokončení [__board__]", + "act-almostdue": "připomínal(a) , že stávající termín dokončení (__timeValue__) __card__ se blíží", + "act-pastdue": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ byl v minulosti", + "act-duenow": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ je teď", + "act-atUserComment": "Byli jste zmíněni v [__board__] __list__/__card__", "delete-user-confirm-popup": "Jste si jisti, že chcete smazat tento účet? Tuto akci nelze vrátit zpět.", + "delete-team-confirm-popup": "Jste si jisti, že chcete smazat tento tým? Tuto akci nelze vrátit zpět.", + "delete-org-confirm-popup": "Jste si jisti, že chcete smazat tuto organizaci? Tuto akci nelze vrátit zpět.", "accounts-allowUserDelete": "Dovolit uživatelům smazat vlastní účet", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", + "hide-minicard-label-text": "Skrýt text popisku minikarty", + "show-desktop-drag-handles": "Zobrazit okraje pro přesun plochy", + "assignee": "Řešitel", + "cardAssigneesPopup-title": "Řešitel", "addmore-detail": "Přidat detailnější popis", "show-on-card": "Zobrazit na kartě", "new": "Nový", + "editOrgPopup-title": "Editovat organizaci", + "newOrgPopup-title": "Nová organizace", + "editTeamPopup-title": "Editovat tým", + "newTeamPopup-title": "Nový tým", "editUserPopup-title": "Editovat uživatele", "newUserPopup-title": "Nový uživatel", "notifications": "Upozornění", "view-all": "Zobrazit vše", - "filter-by-unread": "Filter by Unread", + "filter-by-unread": "Zobrazit nepřečtené", "mark-all-as-read": "Označit vše jako přečtené", + "remove-all-read": "Odstranit přečtené", "allow-rename": "Povolit přejmenování", - "allowRenamePopup-title": "Povolit přejmenování" + "allowRenamePopup-title": "Povolit přejmenování", + "start-day-of-week": "Nastavit první den týdne", + "monday": "Pondělí", + "tuesday": "Úterý", + "wednesday": "Středa", + "thursday": "Čtvrtek", + "friday": "Pátek", + "saturday": "Sobota", + "sunday": "Neděle", + "status": "Stav", + "swimlane": "Dráha", + "owner": "Vlastník", + "last-modified-at": "Naposledy změněno", + "last-activity": "Poslední aktivita", + "voting": "Hlasování", + "archived": "Archivováno", + "delete-linked-card-before-this-card": "Tuto kratu nemůžete odstranit dokud nesmažete přiřazené karty", + "delete-linked-cards-before-this-list": "Tento sloupec nemůžete smazat dokud nesmažete propojené karty, které jsou navázány na karty v tomto sloupci", + "hide-checked-items": "Skrýt zvolené položky", + "task": "Úkol", + "create-task": "Vytvořit úkol", + "ok": "OK", + "organizations": "Organizace", + "teams": "Týmy", + "displayName": "Zobrazovaný název", + "shortName": "Krátký název", + "website": "Webová stránka", + "person": "Osoba", + "my-cards": "Moje karty", + "card": "Karta", + "board": "Tablo", + "context-separator": "/", + "myCardsSortChange-title": "Pořadí mých karet", + "myCardsSortChangePopup-title": "Pořadí mých karet", + "myCardsSortChange-choice-board": "Podle tabla", + "myCardsSortChange-choice-dueat": "Podle termínu", + "dueCards-title": "Termín karet", + "dueCardsViewChange-title": "Náhled termínu karet", + "dueCardsViewChangePopup-title": "Náhled termínu karet", + "dueCardsViewChange-choice-me": "Moje", + "dueCardsViewChange-choice-all": "Všechny", + "dueCardsViewChange-choice-all-description": "Zobrazí všechny nedokončené karty s *Termínem dokončení* z každého tabla, ke kterému má uživatel oprávnění.", + "broken-cards": "Rozbité karty", + "board-title-not-found": "Tablo '%s' nenalezeno.", + "swimlane-title-not-found": "Swimlane '%s' nenalezena.", + "list-title-not-found": "Sloupec '%s' nenalezen.", + "label-not-found": "Štítek '%s' nenalezen.", + "label-color-not-found": "Barva štítku %s nenalezena.", + "user-username-not-found": "Uživatelské jméno '%s' nenalezeno.", + "comment-not-found": "Karta s komentářem obsahujícím text '%s' nenalezena.", + "globalSearch-title": "Vyhledat všechna tabla", + "no-cards-found": "Žádné karty nebyly nalezeny", + "one-card-found": "Nalezena jedna karta", + "n-cards-found": "nalezeny %s karty", + "n-n-of-n-cards-found": "__start__-__end__ z __total__ karet nalezeno", + "operator-board": "tablo", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "sloupce", + "operator-list-abbrev": "l", + "operator-label": "štítek", + "operator-label-abbrev": "#", + "operator-user": "uživatel", + "operator-user-abbrev": "@", + "operator-member": "člen", + "operator-member-abbrev": "m", + "operator-assignee": "řešitel", + "operator-assignee-abbrev": "a", + "operator-creator": "tvůrce", + "operator-status": "stav", + "operator-due": "do", + "operator-created": "vytvořeno", + "operator-modified": "modifikováno", + "operator-sort": "třídění", + "operator-comment": "komentář", + "operator-has": "má", + "operator-limit": "limit", + "predicate-archived": "archivováno", + "predicate-open": "otevřít", + "predicate-ended": "ukončeno", + "predicate-all": "vše", + "predicate-overdue": "po termínu", + "predicate-week": "týden", + "predicate-month": "měsíc", + "predicate-quarter": "čtvrtletí", + "predicate-year": "rok", + "predicate-due": "do", + "predicate-modified": "modifikováno", + "predicate-created": "vytvořeno", + "predicate-attachment": "příloha", + "predicate-description": "popis", + "predicate-checklist": "zaškrtávací seznam", + "predicate-start": "začátek", + "predicate-end": "konec", + "predicate-assignee": "řešitel", + "predicate-member": "člen", + "predicate-public": "veřejný", + "predicate-private": "soukromý", + "operator-unknown-error": "%s není operátor", + "operator-number-expected": "operátor __operator__ očekával číslo, ale dostal '__value__'", + "operator-sort-invalid": "pořadí '%s' není platné", + "operator-status-invalid": "'%s' není platný stav", + "operator-has-invalid": "'%s' není platná kontrola existence", + "operator-limit-invalid": "není platný limit. Limit musí být pozitivní číslo.", + "next-page": "Následující stránka", + "previous-page": "Předchozí stránka", + "heading-notes": "Poznámky", + "globalSearch-instructions-heading": "Vyhledat instrukce", + "globalSearch-instructions-description": "Vyhledávání lze za účelem zacílení doplnit o operátory. Operátory jsou zadávány napsáním názvu operátoru a hodnoty, které jsou odděleny dvojtečkou. Například specifikace operátoru `list:Blocked` omezí vyhledávání na karty obsažené ve sloupci *Blocked*. Pokud hodnota obsahuje mezerynebo speciální znaky, musí se nacházet v uvozovkách (např. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Dostupné operátory:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - karty v tablech odpovídajících zadanému *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - karty ve sloupcích odpovídajících zadanému *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - karty v drahách odpovídajících zadanému *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - karty s komentářem obsahujícím *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - karty, které mají štítek *<color>* nebo *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - zkratka pro `__operator_label__:<color>` nebo `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - karty, kde *<username>* je *member* nebo *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - zkratka pro `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - karty, kde *<username>* je *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - karty, kde *<username>* je *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - karty, kde *<username>* je tvůrcem", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - karty, které jsou *<n>* dny před termínem. `__operator_due__:__predicate_overdue__ zobrazí všechny karty po termínu.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - karty, které byly vytvořeny před *<n>* nebo méně dny", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - katry, které byly upraveny před *<n>* nebo méně dny ", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - kde *<status>* je jedno z následujících:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archivované karty", + "globalSearch-instructions-status-all": "`__predicate_all__` - všechny archivované a nearchivované karty", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - karty s datem dokončení", + "globalSearch-instructions-status-public": "`__predicate_public__` - karty na veřejných tablech", + "globalSearch-instructions-status-private": "`__predicate_private__` - karty jen na soukromých tablech", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - kde *<field>* je jedno z `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` nebo `__predicate_member__`. Zadáním `-` před *<field>* vyhledáte karty, které dané políčko nemají zadané (např. `has:-due` vyhledá karty bez zadaného termínu).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - kde *<sort-name>* je jedno z `__predicate_due__`, `__predicate_created__` nebo `__predicate_modified__`. Pro sestupné řazení zadejte před název `-`.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - kde *<n>* je kladné celé číslo vyjadřující počet karet zobrazených na jedné stránce.", + "globalSearch-instructions-notes-1": "Lze zadat více operátorů současně.", + "globalSearch-instructions-notes-2": "Podobné operátory jsou spojeny pomocí *OR*. Jsou zobrazeny karty odpovídající kterékoli v podmínek.\n`__operator_list__:Available __operator_list__:Blocked` zobrazí karty obsažené v jakémkoli sloupci s názvem *Blocked* nebo *Available*.", + "globalSearch-instructions-notes-3": "Odlišné operátory jsou spojeny pomocí *AND*. Jsou zobrazeny pouze karty, které odpovídají všem definovaným operátorům. `__operator_list__:Available __operator_label__:red` zobrazí pouze karty ve sloupci *Available* with a štítkem *red*.", + "globalSearch-instructions-notes-3-2": "Dny mohou být specifikovány jako pozitivní nebo negativní číslic, nebo použitím `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Vyhledávání rozlišuje velikost písmen.", + "globalSearch-instructions-notes-5": "Ve výchozím zobrazení nejsou vyhledávány archivované položky.", + "link-to-search": "Odkaz na toto vyhledávání", + "excel-font": "Arial", + "number": "Číslo", + "label-colors": "Štítek barvy", + "label-names": "Štítek jména", + "archived-at": "archivováno", + "sort-cards": "Třídit", + "cardsSortPopup-title": "Třídit", + "due-date": "Požadovaný termín", + "server-error": "Chyba na serveru", + "server-error-troubleshooting": "Odešlete prosím chybu, kterou server vygeneroval.\nV případě instalace přes snap spusťte: `sudo snap logs wekan.wekan`\nV případě instalace přes Docker spusťte: `sudo docker logs wekan-app`", + "title-alphabetically": "Nadpis (Abecedně)", + "created-at-newest-first": "Vyvtořeno (Od nejnovějších)", + "created-at-oldest-first": "Vytvořeno (Od nejstarších)", + "links-heading": "Odkazy", + "hide-system-messages-of-all-users": "Skrýt systémové zprávy všech uživatelů", + "now-system-messages-of-all-users-are-hidden": "Systémové zprávy všech uživatelů jsou nyní skryté", + "move-swimlane": "Přesunout dráhu", + "moveSwimlanePopup-title": "Přesunout dráhu", + "custom-field-stringtemplate": "Šablona řetězce", + "custom-field-stringtemplate-format": "Formát (jako placeholder použijte %{value})", + "custom-field-stringtemplate-separator": "Oddělovač (pro mezeru použijte nebo  )", + "custom-field-stringtemplate-item-placeholder": "Stiskněte enter pro přidání více položek", + "creator": "Tvůrce", + "filesReportTitle": "Report souborů", + "orphanedFilesReportTitle": "Report osiřelých souborů", + "reports": "Reporty", + "rulesReportTitle": "Report pravidel", + "copy-swimlane": "Kopírovat dráhu", + "copySwimlanePopup-title": "Kopírovat dráhu", + "display-card-creator": "Zobrazovat tvůrce karet", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximalizovat kartu", + "minimize-card": "Minimalizovat kartu", + "delete-org-warning-message": "Tuto organizaci není možné smazat, protože do ní patří uživatel(é)", + "delete-team-warning-message": "Tento tým není možné smazat, protože do nej patří uživatel(é)" } \ No newline at end of file diff --git a/i18n/da.i18n.json b/i18n/da.i18n.json index 244ee00d2..83521b58b 100644 --- a/i18n/da.i18n.json +++ b/i18n/da.i18n.json @@ -1,769 +1,1062 @@ { - "accept": "Accepter", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "accept": "Acceptér", + "act-activity-notify": "Aktivitetsnotits", + "act-addAttachment": "tilføjede vedhæftningen __attachment__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-deleteAttachment": "slettede vedhæftning __attachment__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-addSubtask": "tilføjede delopgaven __subtask__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-addLabel": "Tilføjede etiketten __label__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-addedLabel": "Tilføjede etiketten __label__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-removeLabel": "Fjernede etiketten __label__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på kortet __board__", + "act-removedLabel": "Fjernede etiketten __label__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på kortet __board__", + "act-addChecklist": "tilføjede tjeklisten __checklist__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-addChecklistItem": "tilføjede elementet i tjekliste __checklistItem__ til tjeklisten __checklist__ i kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-removeChecklist": "fjernede tjeklisten __checklist__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-removeChecklistItem": "fjernede elementet i tjekliste __checklistItem__ fra tjeklisten __checkList__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-checkedItem": "markerede __checklistItem__ fra tjeklisten __checklist__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-uncheckedItem": "afmarkerede __checklistItem__ fra tjeklisten __checklist__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-completeChecklist": "afsluttet tjekliste __checklist__ i kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-uncompleteChecklist": "uafsluttet tjekliste __checklist__ i kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-addComment": "kommenterede på kortet __card__: __comment__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-editComment": "redigerede kommentar på kortet __card__: __comment__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-deleteComment": "slettede kommentar på kortet __card__: __comment__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-createBoard": "oprettede tavlen __board__", + "act-createSwimlane": "oprettede svømmebanen __swimlane__ på tavlen __board__", + "act-createCard": "oprettede kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-createCustomField": "oprettede brugerdefineret felt __customField__ på tavlen __board__", + "act-deleteCustomField": "slettede brugerdefineret felt __customField__ på tavlen __board__", + "act-setCustomField": "redigerede brugerdefineret felt __customField__: __customFieldValue__ i kortet __card__ på lsten __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-createList": "tilføjede listen __list__ til tavlen __board__", + "act-addBoardMember": "tilføejede medlemmet __member__ til tavlen __board__", + "act-archivedBoard": "Tavlen __board__ blev flyttet til Arkiv", + "act-archivedCard": "Kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__ er flyttet til arkivet", + "act-archivedList": "Listen __list__ i svømmebanen __swimlane__ på tavlen __board__ er flyttet til arkivet", + "act-archivedSwimlane": "Svømmebanen __swimlane__ på tavlen __board__ er flyttet til arkivet", + "act-importBoard": "importerede tavlen __board__", + "act-importCard": "importerede kortet __card__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-importList": "importerede listen __list__ til svømmebanen __swimlane__ på tavlen __board__", + "act-joinMember": "tilføjede medlemmet __member__ til kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-moveCard": "flyttede kortet __card__ på tavlen __board__ fra listen __oldList__ i svømmebanen __oldSwimlane__ til listen __list__ i svømmebanen __swimlane__", + "act-moveCardToOtherBoard": "flyttede kortet __card__ fra listen __oldList__ i svømmebanen __oldSwimlane__ på tavlen __oldBoard__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-removeBoardMember": "fjernede medlemmet __member__ fra tavlen __board__", + "act-restoredCard": "genskabte kortet __card__ til listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "act-unjoinMember": "fjernede medlemmet __member__ fra kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", - "activities": "Activities", - "activity": "Activity", - "activity-added": "added %s to %s", - "activity-archived": "%s moved to Archive", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-card": "Add Card", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "add-cover": "Add Cover", - "add-label": "Add Label", - "add-list": "Add List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Members", + "actions": "Handlinger", + "activities": "Aktiviteter", + "activity": "Aktivitet", + "activity-added": "tilføjede %s til %s", + "activity-archived": "%s flyttet til Arkiv", + "activity-attached": "vedhæftede %s til %s", + "activity-created": "oprettede %s", + "activity-customfield-created": "oprettede brugerdefineret felt %s", + "activity-excluded": "ekskluderet %s fra %s", + "activity-imported": "importerede %s ind i %s fra %s", + "activity-imported-board": "importerede %s fra %s", + "activity-joined": "indgik i %s", + "activity-moved": "flyttede %s fra %s til %s", + "activity-on": "per %s", + "activity-removed": "fjernede %s fra %s", + "activity-sent": "sendte %s til %s", + "activity-unjoined": "udgik fra %s", + "activity-subtask-added": "tilføjede delopgave til %s", + "activity-checked-item": "afkrydsede %s i tjeklisten %s af %s", + "activity-unchecked-item": "fjernede kryds %s i tjeklisten %s af %s", + "activity-checklist-added": "tilføjede tjeklisten til %s", + "activity-checklist-removed": "fjernede en tjekliste fra %s", + "activity-checklist-completed": "færdiggjorde tjekliste %s af %s", + "activity-checklist-uncompleted": "gjorde tjeklisten ukomplet, %s af %s", + "activity-checklist-item-added": "tilføjede element i tjekliste til '%s' i %s", + "activity-checklist-item-removed": "fjernede element i tjekliste fra '%s' i %s", + "add": "Tilføj", + "activity-checked-item-card": "markerede %s i tjeklisten %s", + "activity-unchecked-item-card": "afmarkerede %s i tjeklisten %s", + "activity-checklist-completed-card": "udført tjekliste __checklist__ i kortet __card__ på listen __list__ i svømmebanen __swimlane__ på tavlen __board__", + "activity-checklist-uncompleted-card": "gjorde tjeklisten ukomplet %s", + "activity-editComment": "redigerede kommentar %s", + "activity-deleteComment": "slettede kommentar %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Tilføj vedhæftning", + "add-board": "Tilføj tavle", + "add-template": "Add Template", + "add-card": "Tilføj kort", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Tilføj svømmebane", + "add-subtask": "Tilføj delopgave", + "add-checklist": "Tilføj tjekliste", + "add-checklist-item": "Tilføj et element til tjeklisten", + "add-cover": "Tilføj omslag", + "add-label": "Tilføj etikette", + "add-list": "Tilføj liste", + "add-members": "Tilføj medlemmer", + "added": "Tilføjet", + "addMemberPopup-title": "Medlemmer", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", - "back": "Back", - "board-change-color": "Change color", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-lists": "Lists", - "bucket-example": "Like “Bucket List” for example", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", + "admin-desc": "Kan se og redigere kort, fjerne medlemmer og ændre indstillinger for tavlen.", + "admin-announcement": "Annoncering", + "admin-announcement-active": "Aktivér annoncering på tværs af systemet", + "admin-announcement-title": "Annoncering fra administrator", + "all-boards": "Alle tavler", + "and-n-other-card": "Samt __count__ andre kort", + "and-n-other-card_plural": "Samt __count__ andre kort", + "apply": "Anvend", + "app-is-offline": "Indlæser, vent venligst. Genopfriskes siden er der risiko for tab af data. Fungerer indlæsningen ikke, så tjek venligst om serveren er stoppet. ", + "archive": "Flyt til arkiv", + "archive-all": "Flyt alle til arkiv", + "archive-board": "Flyt tavle til arkiv", + "archive-card": "Flyt kort til arkiv", + "archive-list": "Flyt liste til arkiv", + "archive-swimlane": "Flyt svømmebane til arkiv", + "archive-selection": "Flyt valgte til arkiv", + "archiveBoardPopup-title": "Flyt tavle til arkiv?", + "archived-items": "Arkiv", + "archived-boards": "Tavler i arkiv", + "restore-board": "Genskab tavle", + "no-archived-boards": "Ingen tavler i arkiv", + "archives": "Arkiv", + "template": "Skabelon", + "templates": "Skabeloner", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Tilknyt medlem", + "attached": "vedhæftet", + "attachment": "Vedhæftning", + "attachment-delete-pop": "Slettes en vedhæftning sker det permanent. Det kan ikke omgøres. ", + "attachmentDeletePopup-title": "Slet vedhæftning?", + "attachments": "Vedhæftninger", + "auto-watch": "Følg automatisk tavler når de oprettes ", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Tilbage", + "board-change-color": "Skift farve", + "board-nb-stars": "%s stjerner", + "board-not-found": "Fandt ikke tavle ", + "board-private-info": "Denne tavle vil være <strong>privat</strong>.", + "board-public-info": "Denne tavle vil være <strong>offentlig</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Skift tavlens baggrund", + "boardChangeTitlePopup-title": "Omdøb tavle", + "boardChangeVisibilityPopup-title": "Tilpas synlighed", + "boardChangeWatchPopup-title": "Tilpas følgefunktion", + "boardMenuPopup-title": "Tavleindstillinger", + "boardChangeViewPopup-title": "Tavlevisning", + "boards": "Tavler", + "board-view": "Tavlevisning", + "board-view-cal": "Kalender", + "board-view-swimlanes": "Svømmebaner", + "board-view-collapse": "Sammenfold", + "board-view-gantt": "Gantt", + "board-view-lists": "Lister", + "bucket-example": "Eksempelvis \"Bucked-liste\"", + "cancel": "Annullér", + "card-archived": "Dette kort blev flyttet til arkivet.", + "board-archived": "Denne tavle blev flyttet til arkivet.", + "card-comments-title": "Dette kort har %s kommentar.", + "card-delete-notice": "Sletning vil være permanent. Du mister alle handlinger knyttet til dette kort.", + "card-delete-pop": "Alle handlinger vil blive fjernet fra aktivitetsfeedet, og du kan ikke genåbne kortet. Det kan ikke omgøres.", + "card-delete-suggest-archive": "Du kan flytte et kort til arkivet for at fjerne det fra tavlen, og bevare aktiviteten.", + "card-due": "Forfalder", + "card-due-on": "Forfaldsdato", + "card-spent": "Anvendt tid", + "card-edit-attachments": "Redigér vedhæftninger", + "card-edit-custom-fields": "Redigér brugerdefinerede felter", + "card-edit-labels": "Redigér etiketter", + "card-edit-members": "Redigér medlemmer", + "card-labels-title": "Ændr etiketter for kortet.", + "card-members-title": "Tilføj eller fjern medlemmer på tavlen fra kortet.", "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardDeletePopup-title": "Delete Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", - "color-indigo": "indigo", + "card-start-on": "Starter per", + "cardAttachmentsPopup-title": "Vedhæft fra", + "cardCustomField-datePopup-title": "Ændringsdato", + "cardCustomFieldsPopup-title": "Redigér brugerdefinerede felter", + "cardStartVotingPopup-title": "Start en stemmeafgivning", + "positiveVoteMembersPopup-title": "Tilhængere", + "negativeVoteMembersPopup-title": "Modstandere", + "card-edit-voting": "Redigér stemmeafgivelse", + "editVoteEndDatePopup-title": "Skift slutdato for afstemning", + "allowNonBoardMembers": "Tillad alle brugere som er logget ind", + "vote-question": "Spørgsmål til afstemning", + "vote-public": "Vis hvem som stemte hvad", + "vote-for-it": "går ind for", + "vote-against": "går imod", + "deleteVotePopup-title": "Slet afstemning?", + "vote-delete-pop": "Sletning er permanent. Du mister alle handlinger knyttet til denne afstemning.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Slet kort?", + "cardDetailsActionsPopup-title": "Handlinger for kort", + "cardLabelsPopup-title": "Etiketter", + "cardMembersPopup-title": "Medlemmer", + "cardMorePopup-title": "Mere", + "cardTemplatePopup-title": "Opret skabelon", + "cards": "Kort", + "cards-count": "Kort", + "cards-count-one": "Kort", + "casSignIn": "Log ind med CAS", + "cardType-card": "Kort", + "cardType-linkedCard": "Sammenkædet kort", + "cardType-linkedBoard": "Sammenkædet tavle", + "change": "Tilpas", + "change-avatar": "Tilpas avatar", + "change-password": "Skift kodeord", + "change-permissions": "Tilpas tilladelser", + "change-settings": "Tilpas indstillinger", + "changeAvatarPopup-title": "Tilpas avatar", + "changeLanguagePopup-title": "Skift sprog", + "changePasswordPopup-title": "Skift kodeord", + "changePermissionsPopup-title": "Tilpas tilladelser", + "changeSettingsPopup-title": "Tilpas indstillinger", + "subtasks": "Delopgaver", + "checklists": "Tjeklister", + "click-to-star": "Klik for at tilføje stjerne til tavlen.", + "click-to-unstar": "Klik for at fjerne stjerne fra tavlen.", + "clipboard": "Udklipsholder eller træk-og-slip", + "close": "Luk", + "close-board": "Luk tavle", + "close-board-pop": "Du har mulighed for at genskabe tavlen ved at klikke på \"Arkiv\"-knappen fra overskriften hjem.", + "close-card": "Close Card", + "color-black": "sort", + "color-blue": "blå", + "color-crimson": "crimsonrød", + "color-darkgreen": "mørkegrøn", + "color-gold": "guld", + "color-gray": "grå", + "color-green": "grøn", + "color-indigo": "indigoblå", "color-lime": "lime", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-mistyrose": "lyserød", + "color-navy": "navyblå", "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "bleg turkis", + "color-peachpuff": "ferskenfarvet", "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-plum": "blommefarvet", + "color-purple": "lilla", + "color-red": "rød", + "color-saddlebrown": "saddelbrun", + "color-silver": "sølv", "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "color-slateblue": "blågrå", + "color-white": "hvid", + "color-yellow": "gul", + "unset-color": "Nulstil", + "comment": "Kommentér", + "comment-placeholder": "Skriv kommentar", + "comment-only": "Kun kommentarer", + "comment-only-desc": "Kan kun kommentere på kort.", + "no-comments": "Ingen kommentarer", + "no-comments-desc": "Kan ikke se kommentarer og aktiviteter.", + "worker": "Arbejder", + "worker-desc": "Kan kun flytte kort, tildele sig selv til kort og kommentere.", "computer": "Computer", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", - "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "confirm-subtask-delete-dialog": "Er du sikker på du vil slette delopgaven?", + "confirm-checklist-delete-dialog": "Er du sikker på du vil slette tjeklisten?", + "copy-card-link-to-clipboard": "Kopiér link til kort til udklipsholder ", + "linkCardPopup-title": "Sammenkæd kort", + "searchElementPopup-title": "Søg", + "copyCardPopup-title": "Kopiér kort", + "copyChecklistToManyCardsPopup-title": "Kopiér tjeklisteskabelon til flere kort", + "copyChecklistToManyCardsPopup-instructions": "Destination for kortenes titler og beskrivelser i dette JSON-format", "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", + "create": "Opret", + "createBoardPopup-title": "Opret tavle", + "chooseBoardSourcePopup-title": "Importér tavle", + "createLabelPopup-title": "Opret etikette", + "createCustomField": "Opret felt", + "createCustomFieldPopup-title": "Opret felt", + "current": "nuværende", + "custom-field-delete-pop": "Du kan ikke fortryde handlingen. Dette vil fjerne dette brugerdefinerede felt fra alle kort og tilintetgøre dens historik.", + "custom-field-checkbox": "Afkrydsningsfelt", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Dato", + "custom-field-dropdown": "Rullegardinliste", + "custom-field-dropdown-none": "(ingen)", + "custom-field-dropdown-options": "Tilvalg for liste", + "custom-field-dropdown-options-placeholder": "Tryk enter for at tilføje flere tilvalg", + "custom-field-dropdown-unknown": "(ukendt)", + "custom-field-number": "Tal", + "custom-field-text": "Tekst", + "custom-fields": "Brugerdefinerede felter", + "date": "Dato", + "decline": "Afslå", + "default-avatar": "Standard-avatar", + "delete": "Slet", + "deleteCustomFieldPopup-title": "Slet brugerdefineret felt?", + "deleteLabelPopup-title": "Slet etikette?", + "description": "Beskrivelse", + "disambiguateMultiLabelPopup-title": "Tydeliggør handling for etikette", + "disambiguateMultiMemberPopup-title": "Tydeliggør handling for medlem", + "discard": "Forkast", + "done": "Færdig", + "download": "Hent", + "edit": "Redigér", + "edit-avatar": "Tilpas avatar", + "edit-profile": "Redigér profil", + "edit-wip-limit": "Redigér WIP-begrænsning", + "soft-wip-limit": "Blød WIP-begrænsning", + "editCardStartDatePopup-title": "Skift startdato", + "editCardDueDatePopup-title": "Skift forfaldsdato", + "editCustomFieldPopup-title": "Redigér felt", + "editCardSpentTimePopup-title": "Tilpas forbrugt tid", + "editLabelPopup-title": "Skift etikette", + "editNotificationPopup-title": "Redigér notifikation", + "editProfilePopup-title": "Redigér profil", + "email": "E-mail", + "email-enrollAccount-subject": "Der er oprettet konto til dig på __siteName__", + "email-enrollAccount-text": "Hej __user__,\n\nFor at begynde at benytte tjenesten, så klik linket nedenfor.\n\n__url__\n\nTak.", + "email-fail": "Afsendelse af e-mail mislykkedes", + "email-fail-text": "Fejl under afsendelse af e-mail", + "email-invalid": "Ugyldig e-mail", + "email-invite": "Invitér via e-mail", + "email-invite-subject": "__inviter__ sendte dig en invitation", + "email-invite-text": "Kære __user__,\n\n__inviter__ inviterer dig til deltagelse i tavlen \"__board__\" for samarbejde.\n\nFølg venligst linket nedenfor:\n\n__url__\n\nTak.", + "email-resetPassword-subject": "Genskab dit kodeord på __siteName__", + "email-resetPassword-text": "Hej __user__,\n\nFor at genskabe dit kodeord, så klik linket nedenfor your password.\n\n__url__\n\nTak.", + "email-sent": "E-mail er afsendt", + "email-verifyEmail-subject": "Verificér din e-mailadresse på your __siteName__", + "email-verifyEmail-text": "Hej __user__,\n\nFor at verificere din e-mail for kontoen, så klik på linket nedenfor.\n\n__url__\n\nTak.", + "enable-wip-limit": "Slå WIP-begrænsning til", + "error-board-doesNotExist": "Denne tavle eksisterer ikke.", + "error-board-notAdmin": "Du skal være administrator for tavlen for at gøre dette", + "error-board-notAMember": "Du skal være medlem af denne tavle for at gøre dette", + "error-json-malformed": "Din tekst er ikke gyldig JSON", + "error-json-schema": "Dine JSON-data indeholder ikke den rette information i det rette format", + "error-csv-schema": "Din CSV (kommaseparerede værdier)/TSV (Tabulatorseparerede værdier) indeholder ikke den rette information i det korrekte format", + "error-list-doesNotExist": "Listen findes ikke", + "error-user-doesNotExist": "Brugeren findes ikke", + "error-user-notAllowSelf": "Du kan ikke invitere dig selv", + "error-user-notCreated": "Brugeren er ikke oprettet", + "error-username-taken": "Brugernavnet er optaget", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "E-mailadressen er allerede optaget", + "export-board": "Eksportér tavle", + "export-board-json": "Eksportér tavle til JSON", + "export-board-csv": "Eksportér tavle til CSV", + "export-board-tsv": "Eksportér tavle til TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Eksportér tavle til HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Eksportér tavle", + "exportCardPopup-title": "Export card", + "sort": "Sortér", + "sort-desc": "Klik for at sortere listen", + "list-sort-by": "Sortér listen efter:", + "list-label-modifiedAt": "Senest tilgået:", + "list-label-title": "Navn på listen", + "list-label-sort": "Din manuelle ordre", + "list-label-short-modifiedAt": "(S)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-no-label": "No label", - "filter-no-member": "No member", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "hide-system-messages": "Hide system messages", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", + "filter-cards": "Filtrér kort eller lister", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filtrér lister efter titel", + "filter-clear": "Ryd filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "Ingen etikette", + "filter-member-label": "Filter by member", + "filter-no-member": "Ingen medlemmer", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "Utildelt", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "Ingen brugerdefinerede felter", + "filter-show-archive": "Vis arkiverede lister", + "filter-hide-empty": "Skjul tomme lister", + "filter-on": "Filter er slået til", + "filter-on-desc": "Du filtrerer kort på denne tavle. Klik her for at redigere filteret.", + "filter-to-selection": "Filtrér til valgte", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Avanceret filter", + "advanced-filter-description": "Avanceret filter gør det muligt at skrive en tekststreng indeholdende følgende operatører: == != <= >= && || ( ) Mellemrum anvendes som adskillelsestegn mellem operatørerne. Du kan filtrere alle Brugerdefinerede felter ved at taste deres navne og værdier. Som eksempel: Felt1 == Værdi1. Bemærk: Hvis felter eller værdier indeholder mellemrum, så skal du indkapsle dem i enkeltcitationstegn. Som eksempel: 'Felt 1' == 'Værdi1'. For at springe over enkelte kontroltegn (' \\/), så kan \\ benyttes. Som eksempel: Felt1 == Så\\'n. Du kan også kombinere flere betingelser. Som eksempel: F1 == V1 || F1 == V2. Normalt vil alle operatører blive fortolket fra venstre mod højre. Du kan ændre rækkefølgen ved brug af parenteser. Som eksempel: F1 == V1 && (F2 == V2 || F2 == V3). Du kan også søge i tekstfelter med brug af regulære udtryk: F1 == /Tes.*/i", + "fullname": "Fuldt navn", + "header-logo-title": "Gå tilbage til siden med dine tavler", + "hide-system-messages": "Skjul systembeskeder", + "headerBarCreateBoardPopup-title": "Opret tavle", + "home": "Hjem", + "import": "Importér", + "impersonate-user": "Impersonate user", "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", + "import-board": "importér tavle", + "import-board-c": "Importér tavle", + "import-board-title-trello": "Importér tavle fra Trello", + "import-board-title-wekan": "Importér tavler fra tidligere eksport", + "import-board-title-csv": "Importér tavle fra CSV/TSV", + "from-trello": "Fra Trello", + "from-wekan": "Fra forrige eksport", + "from-csv": "Fra CSV/TSV", + "import-board-instruction-trello": "I din Trello-tavle, gå til 'Menu', dernæst 'More', 'Print and Export', 'Export JSON', og kopiér den tekst som vises.", + "import-board-instruction-csv": "Indsæt dine kommaseparerede værdier (CSV)/ Tabulatorseparerede værdier (TSV).", + "import-board-instruction-wekan": "På din tavle, gå til 'Menu', dernæst 'Eksportér tavle', og kopiér teksten i den hentede fil.", + "import-board-instruction-about-errors": "Hvis du får fejl når der importeres en tavle, så vil importen undertiden stadig fungere, og tavlen vil være under side Alle tavler.", + "import-json-placeholder": "Indsæt dine gyldige JSON-data her", + "import-csv-placeholder": "Indsæt din valide CSV/TSV-data her", + "import-map-members": "Kortlæg medlemmer", + "import-members-map": "Dine importerede tavler rummer medlemmer. Kortlæg venligst de medlemmer du ønsker at importere til dine brugere.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Gennemse kortlægning af medlemmer", + "import-user-select": "Vælg din eksisterende bruger, som du ønsker at bruge for dette medlem", + "importMapMembersAddPopup-title": "Vælg medlem", "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "members": "Members", + "initials": "Initialer", + "invalid-date": "Ugyldig dato", + "invalid-time": "Ugyldig tidsangivelse", + "invalid-user": "Ugyldig bruger", + "joined": "sluttede sig til", + "just-invited": "Du er netop blevet inviteret til denne tavle", + "keyboard-shortcuts": "Tastaturgenveje", + "label-create": "Opret etikette", + "label-default": "%s etikette (standard)", + "label-delete-pop": "Det er ikke muligt at fortryde. Dette vil fjerne etiketten fra alle kort og ødelægge dets historik.", + "labels": "Etiketter", + "language": "Sprog", + "last-admin-desc": "Du kan ikke ændre roller, da der mindst skal være én administrator.", + "leave-board": "Forlad tavle", + "leave-board-pop": "Er du sikker på du vil forlade __boardTitle__? Du vil blive fjernet fra alle kort på denne tavle.", + "leaveBoardPopup-title": "Forlad tavle?", + "link-card": "Link til dette kort", + "list-archive-cards": "Flyt alle kort i denne liste til arkivet", + "list-archive-cards-pop": "Dette vil fjerne alle kort i denne liste fra tavlen. For at se kort i arkivet og bringe dem tilbage til tavlen, så klik \"Menu\" > \"Arkiv\".", + "list-move-cards": "Flyt alle kort i denne liste", + "list-select-cards": "Vælg alle kort i denne liste", + "set-color-list": "Angiv farve", + "listActionPopup-title": "Handlinger for liste", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Handlinger for svømmebane", + "swimlaneAddPopup-title": "Tilføj en svømmebane nedenfor", + "listImportCardPopup-title": "Importér et Trello-kort", + "listImportCardsTsvPopup-title": "Importér Excel CSV/TSV", + "listMorePopup-title": "Mere", + "link-list": "Link til denne liste", + "list-delete-pop": "Alle handlinger vil blive fjernet fra aktivitetsfeedet og du vil ikke have mulighed for at genskabe listen. Der er ingen måder at fortryde. ", + "list-delete-suggest-archive": "Du kan flytte en liste til arkivet for at fjerne det fra tavlen og bevare dets aktivitet.", + "lists": "Lister", + "swimlanes": "Svømmebaner", + "log-out": "Log ud", + "log-in": "Log ind", + "loginPopup-title": "Log ind", + "memberMenuPopup-title": "Medlemsindstillinger", + "members": "Medlemmer", "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "multi-selection": "Multi-Selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", + "move-selection": "Flyt valgte", + "moveCardPopup-title": "Flyt kort", + "moveCardToBottom-title": "Flyt til bunden", + "moveCardToTop-title": "Flyt til toppen", + "moveSelectionPopup-title": "Flyt valgte", + "multi-selection": "Multivalg", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multivalg er slået til", + "muted": "Slukket", + "muted-info": "Du vil aldrig få notifikationer om ændringer i denne tavle", + "my-boards": "Mine tavler", + "name": "Navn", + "no-archived-cards": "Ingen kort i arkivet.", + "no-archived-lists": "Ingen lister i arkivet.", + "no-archived-swimlanes": "Ingen svømmebaner i arkivet.", + "no-results": "Ingen resultater", "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creater or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove Cover", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", + "normal-desc": "Du kan se og redigere kort. Indstillinger kan ikke ændres.", + "not-accepted-yet": "Invitation er endnu ikke accepteret", + "notify-participate": "Modtag opdateringer for alle kort du deltager i, som opretter eller medlem ", + "notify-watch": "Modtag opdateringer for alle tavler eller kort som du følger ", + "optional": "valgfri", + "or": "eller", + "page-maybe-private": "Denne side kan være privat. Du kan eventuelt se den ved at <a href='%s'>logge ind</a>.", + "page-not-found": "Siden blev ikke fundet.", + "password": "Kodeord", + "paste-or-dragdrop": "for at indsætte eller træk-og-slip billedfilen til den (kun billede)", + "participating": "Deltager", + "preview": "Forhåndsvisning", + "previewAttachedImagePopup-title": "Forhåndsvisning", + "previewClipboardImagePopup-title": "Forhåndsvisning", + "private": "Privat", + "private-desc": "Denne tavle er privat. Det er kun tilføjede personer som kan se og redigere den. ", + "profile": "Profil", + "public": "Offentlig", + "public-desc": "Denne tavle er offentlig. Den er synlig for alle med linket og vil blive vist i søgemaskiner som Google. Det er kun personer tilføjet til tavlen, der kan redigere. ", + "quick-access-description": "Stjernemarkér en tavle for at tilføje genvej i denne bjælke.", + "remove-cover": "Fjern omslag", + "remove-from-board": "Fjern fra tavle", + "remove-label": "Fjern etikette", + "listDeletePopup-title": "Slet liste?", + "remove-member": "Fjern medlem", + "remove-member-from-card": "Fjern fra kort", + "remove-member-pop": "Fjern __name__ (__username__) fra __boardTitle__? Medlemmet vil blive fjernetfra alle kort på denne tavle. De vil modtage en notifikation.", + "removeMemberPopup-title": "Fjern medlem?", + "rename": "Omdøb", + "rename-board": "Omdøb tavle", + "restore": "Genskab", + "save": "Gem", + "search": "Søg", + "rules": "Regler", + "search-cards": "Søg ud fra titler i kort/lister, beskrivelser og brugerdefinerede felter på denne tavle. ", + "search-example": "Write text you search and press Enter", + "select-color": "Vælg farve", + "select-board": "Select Board", + "set-wip-limit-value": "Angiv en grænse for det maksimale antal opgaver i denne liste", + "setWipLimitPopup-title": "Angiv WIP-begrænsning", + "shortcut-assign-self": "Tilknyt dig selv til nuværende kort?", + "shortcut-autocomplete-emoji": "Auto-fuldfør emoji", + "shortcut-autocomplete-members": "Auto-fuldfør medlemmer", + "shortcut-clear-filters": "Ryd alle filtre", + "shortcut-close-dialog": "Luk dialogboks", + "shortcut-filter-my-cards": "Filtrer mine kort", + "shortcut-show-shortcuts": "Fremvis denne liste med genveje", + "shortcut-toggle-filterbar": "Slå filter-sidebjælke til/fra", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Slå tavle-sidebjælke til/fra", + "show-cards-minimum-count": "Vis antal kort når listen indeholder mere end", + "sidebar-open": "Åbn sidebjælke", + "sidebar-close": "Luk sidebjælke", + "signupPopup-title": "Opret en konto", + "star-board-title": "Klik for at stjernemarkere denne tavle. Den vil blive vist i toppen af din liste over tavler.", + "starred-boards": "Tavler med stjerner", + "starred-boards-description": "Tavler med stjerner vises i toppen af din liste over tavler.", + "subscribe": "Abonnér", "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "this-board": "denne tavle", + "this-card": "dette kort", + "spent-time-hours": "Anvendt tid (timer)", + "overtime-hours": "Overtid (timer)", + "overtime": "Overtid", + "has-overtime-cards": "Har kort med overtid", + "has-spenttime-cards": "Har kort med anvendt tid", + "time": "Tid", + "title": "Titel", + "tracking": "Sporing", + "tracking-info": "Du vil få notifikation om alle ændringer i kort som du har oprettet eller er medlem af.", "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "username": "Username", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value.", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", + "unassign-member": "Fjern medlemstilknytning", + "unsaved-description": "Du har en beskrivelse som ikke er gemt", + "unwatch": "Ophør med at følge", + "upload": "Overfør", + "upload-avatar": "Overfør en avatar", + "uploaded-avatar": "Overførte en avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Brugernavn", + "import-usernames": "Import Usernames", + "view-it": "Vis den", + "warn-list-archived": "advarsel: dette kort er i en liste i arkivet", + "watch": "Følg", + "watching": "Følger", + "watching-info": "Du vil få notifikation om alle ændringer i denne tavle.", + "welcome-board": "Velkomsttavle", + "welcome-swimlane": "Milepæl 1", + "welcome-list1": "Grundlæggende", + "welcome-list2": "Avanceret", + "card-templates-swimlane": "Kortskabeloner", + "list-templates-swimlane": "Listeskabeloner", + "board-templates-swimlane": "Tavleskabeloner", + "what-to-do": "Hvad ønsker du at foretage dig?", + "wipLimitErrorPopup-title": "Ugyldig WIP-begrænsning", + "wipLimitErrorPopup-dialog-pt1": "Antallet af opgaver i denne liste er højere end WIP-begræsningen du har angivet.", + "wipLimitErrorPopup-dialog-pt2": "Flyt venligst nogle af opgaverne ud af listen, eller angiv en højere WIP-begrænsning.", + "admin-panel": "Admin-panel", + "settings": "Indstillinger", + "people": "Personer", + "registration": "Tilmelding", + "disable-self-registration": "Slå selv-tilmelding fra", + "invite": "Invitér", + "invite-people": "Invitér personer", + "to-boards": "Til tavle(r)", + "email-addresses": "E-mailadresser", + "smtp-host-description": "Adressen på SMTP-serveren som håndterer din e-mail.", + "smtp-port-description": "Den port som din SMTP-server benytter til udgående e-mail. ", + "smtp-tls-description": "Slå TLS-understøttelse til for SMTP-serveren", + "smtp-host": "SMTP-vært", + "smtp-port": "SMTP-port", + "smtp-username": "Brugernavn", + "smtp-password": "Kodeord", + "smtp-tls": "TLS-understøttelse", + "send-from": "Fra", + "send-smtp-test": "Send en e-mailtest til dig selv", + "invitation-code": "Invitationskode", + "email-invite-register-subject": "__inviter__ sendte dig en invitation", + "email-invite-register-text": "Kære __user__,\n\n__inviter__ inviterer dig til at samarbejde på kanbantavlen.\n\nFølg venligst linket nedenfor:\n__url__\n\nDin invitationskode er: __icode__\n\nTak.", + "email-smtp-test-subject": "SMTP for e-mailtest", + "email-smtp-test-text": "Afsendelse af e-mail blev udført ", + "error-invitation-code-not-exist": "Invitationskoden findes ikke", + "error-notAuthorized": "Du har ikke tilladelse til at se denne side.", + "webhook-title": "Navn på webhook", + "webhook-token": "Token (valgfri til godkendelse)", + "outgoing-webhooks": "Udgående webhooks", + "bidirectional-webhooks": "To-vejs webhooks", + "outgoingWebhooksPopup-title": "Udgående webhooks", + "boardCardTitlePopup-title": "Filter for korttitel", + "disable-webhook": "Slå denne webhook fra", + "global-webhook": "Globale webhooks", + "new-outgoing-webhook": "Ny udgående webhook", + "no-name": "(Ukendt)", + "Node_version": "Node-version", + "Meteor_version": "Meteor-version", + "MongoDB_version": "MongoDB-version", + "MongoDB_storage_engine": "Lagringsmotor for MongoDB", + "MongoDB_Oplog_enabled": "MongoDB Oplog er slået til", + "OS_Arch": "OS - Arch", + "OS_Cpus": "CPU-antal for OS", + "OS_Freemem": "Fri hukommelse for OS", + "OS_Loadavg": "Gns.belastning for OS", + "OS_Platform": "OS-platform", + "OS_Release": "OS-udgivelse", + "OS_Totalmem": "Samlet hukommelse for OS", + "OS_Type": "OS-type", + "OS_Uptime": "OS-oppetid", + "days": "dage", + "hours": "timer", + "minutes": "minutter", + "seconds": "sekunder", + "show-field-on-card": "Vis dette felt på kortet", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Vis feltetikette på minikort", + "yes": "Ja", + "no": "Nej", + "accounts": "Konti", + "accounts-allowEmailChange": "Tillad ændring af e-mail", + "accounts-allowUserNameChange": "Tillad ændring af brugernavn", + "createdAt": "Oprettet per", + "modifiedAt": "Modified at", + "verified": "Verificeret", + "active": "Aktiv", + "card-received": "Modtaget", + "card-received-on": "Modtaget per", + "card-end": "Slut", + "card-end-on": "Slutter per", + "editCardReceivedDatePopup-title": "Tilpas modtagelsesdato", + "editCardEndDatePopup-title": "Tilpas slutdato", + "setCardColorPopup-title": "Angiv farve", + "setCardActionsColorPopup-title": "Vælg en farve", + "setSwimlaneColorPopup-title": "Vælg en farve", + "setListColorPopup-title": "Vælg en farve", + "assigned-by": "Tildelt af", + "requested-by": "Anmodet af", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Sletning er permanent. Du vil miste alle lister, kort og handlinger knyttet til denne tavle.", + "delete-board-confirm-popup": "Alle lister, kort, etiketter og aktiviteter vil blive slettet og du får ikke mulighed for at genskabe tavlens indhold. Dette kan ikke fortrydes.", + "boardDeletePopup-title": "Slet tavle?", + "delete-board": "Slet tavle", + "default-subtasks-board": "Delopgaver for tavlen __board__", + "default": "Standard", + "queue": "Kø", + "subtask-settings": "Indstillinger for delopgaver", + "card-settings": "Indstillinger for kort", + "boardSubtaskSettingsPopup-title": "Indstillinger for delopgaver i tavle", + "boardCardSettingsPopup-title": "Indstillinger for kort", + "deposit-subtasks-board": "Indsæt delopgaver på denne tavle: ", + "deposit-subtasks-list": "Liste som der landes på, når delopgaver indsættes her:", + "show-parent-in-minicard": "Vis overordnede i minikort:", + "prefix-with-full-path": "Præfiks med fuld sti", + "prefix-with-parent": "Præfiks med overordnede", + "subtext-with-full-path": "Undertekst med fuld sti", + "subtext-with-parent": "Undertekst med overordnede", + "change-card-parent": "Skift kortets overordnede", + "parent-card": "Overordnede kort", + "source-board": "Kilde for tavle", + "no-parent": "Vis ikke overordnede", + "activity-added-label": "tilføjede etiketten '%s' til %s", + "activity-removed-label": "tilføjede etiketten '%s' fra %s", + "activity-delete-attach": "slettede en vedhæftning fra %s", + "activity-added-label-card": "tilføjede etiketten '%s'", + "activity-removed-label-card": "fjernede etiketten '%s'", + "activity-delete-attach-card": "slettede en vedhæftning", + "activity-set-customfield": "angiv brugerdefineret felt '%s' til '%s' i %s", + "activity-unset-customfield": "fjernede brugerdefineret felt '%s' i %s", + "r-rule": "Regel", + "r-add-trigger": "Tilføj trigger-udløser", + "r-add-action": "Tilføj handling", + "r-board-rules": "Regler for tavle", + "r-add-rule": "Tilføj regel", + "r-view-rule": "Vis regel", + "r-delete-rule": "Slet regel", + "r-new-rule-name": "Ny titel for regel", + "r-no-rules": "Ingen regler", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "Når et kort", + "r-is": "er", + "r-is-moved": "flyttes", + "r-added-to": "Tilføjet til", + "r-removed-from": "Fjernes fra", + "r-the-board": "tavlen", + "r-list": "liste", + "list": "List", + "set-filter": "Angiv filter", + "r-moved-to": "Flyttet til", + "r-moved-from": "Flyttet fra", + "r-archived": "Flyttet fra arkiv", + "r-unarchived": "Genskabt fra arkiv", + "r-a-card": "et kort", + "r-when-a-label-is": "Når en etikette er", + "r-when-the-label": "Når etiketten", + "r-list-name": "listenavn", + "r-when-a-member": "Når et medlem er", + "r-when-the-member": "Når medlemmet", + "r-name": "navn", + "r-when-a-attach": "Når en vedhæftning", + "r-when-a-checklist": "Når en tjekliste er", + "r-when-the-checklist": "Når tjeklisten", + "r-completed": "Fuldført", + "r-made-incomplete": "Gjort ukomplet", + "r-when-a-item": "Når et element i tjeklisten er", + "r-when-the-item": "Når elementet i tjeklisten", + "r-checked": "Markeret", + "r-unchecked": "Umarkeret", + "r-move-card-to": "Flyt kort til", + "r-top-of": "Toppen af", + "r-bottom-of": "Bunden af", + "r-its-list": "dens liste", + "r-archive": "Flyt til arkiv", + "r-unarchive": "Genskab fra arkiv", + "r-card": "kort", + "r-add": "Tilføj", + "r-remove": "Fjern", + "r-label": "etikette", + "r-member": "medlem", + "r-remove-all": "Fjern alle medlemmer fra kortet", + "r-set-color": "Angiv farven til", + "r-checklist": "tjekliste", + "r-check-all": "Markér alle", + "r-uncheck-all": "Fravælg alle", + "r-items-check": "elementer fra tjekliste", + "r-check": "Markér", + "r-uncheck": "Fravælg", + "r-item": "element", + "r-of-checklist": "fra tjekliste", + "r-send-email": "Send en e-mail", + "r-to": "til", + "r-of": "af", + "r-subject": "emne", + "r-rule-details": "Detaljer for regel", + "r-d-move-to-top-gen": "Flyt kortet til toppen af dens liste", + "r-d-move-to-top-spec": "Flyt kortet til toppen af listen", + "r-d-move-to-bottom-gen": "Flyt kortet til bunden af dens liste", + "r-d-move-to-bottom-spec": "Flyt kortet til bunden af listen", + "r-d-send-email": "Send e-mail", + "r-d-send-email-to": "til", + "r-d-send-email-subject": "emne", + "r-d-send-email-message": "besked", + "r-d-archive": "Flyt kortet til arkiv", + "r-d-unarchive": "Genskab kort fra arkiv", + "r-d-add-label": "Tilføj etikette", + "r-d-remove-label": "Fjern etikette", + "r-create-card": "Opret nyt kort", + "r-in-list": "i liste", + "r-in-swimlane": "i svømmebane", + "r-d-add-member": "Tilføj medlem", + "r-d-remove-member": "Fjern medlem", + "r-d-remove-all-member": "Fjern alle medlemmer", + "r-d-check-all": "Markér alle elementer fra en liste", + "r-d-uncheck-all": "Fravælg alle elementer fra en liste", + "r-d-check-one": "Markér element", + "r-d-uncheck-one": "Fravælg element", + "r-d-check-of-list": "fra tjekliste", + "r-d-add-checklist": "Tilføj tjekliste", + "r-d-remove-checklist": "Fjern tjekliste", + "r-by": "af", + "r-add-checklist": "Tilføj tjekliste", + "r-with-items": "med elementer", + "r-items-list": "element1,element2,element3", + "r-add-swimlane": "Tilføj svømmebane", + "r-swimlane-name": "navn på svømmebane", + "r-board-note": "Bemærk: lad et felt stå tomt for at matche alle værdier.", + "r-checklist-note": "Bemærk: tjeklistens elementer skal skrives som en kommaadskilte værdier.", + "r-when-a-card-is-moved": "Når et kort flyttes til en anden liste.", + "r-set": "Angiv", + "r-update": "Opdatér", + "r-datefield": "datofelt", "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-df-due-at": "forfalder", + "r-df-end-at": "slutter", + "r-df-received-at": "modtaget", + "r-to-current-datetime": "til nuværende dato/tidspunkt", + "r-remove-value-from": "Fjern værdi fra", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", + "authentication-method": "Godkendelsesmetode", + "authentication-type": "Godkendelsestype", + "custom-product-name": "Tilpasset produktnavn", "layout": "Layout", - "hide-logo": "Hide Logo", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "hide-logo": "Skjul logo", + "add-custom-html-after-body-start": "Tilføj tilpasset HTML efter <body> start", + "add-custom-html-before-body-end": "Tilføj tilpasset HTML før </body> slutning", + "error-undefined": "Noget gik galt", + "error-ldap-login": "Fejl under forsøg på login", + "display-authentication-method": "Vis godkendelsesmetode", + "default-authentication-method": "Standard for godkendelsesmetode", + "duplicate-board": "Duplikér tavle", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "Antallet af personer er:", + "swimlaneDeletePopup-title": "Slet svømmebane?", + "swimlane-delete-pop": "Alle handlinger vil blive fjernet fra aktivitetsfeedet, og du vil ikke kunne genskabe svømmebanen. Dette kan ikke fortrydes.", + "restore-all": "Genskab alle", + "delete-all": "Slet alle", + "loading": "Indlæser, vent venligst", + "previous_as": "seneste tidspunkt var", + "act-a-dueAt": "ændrede forfaldstidspunkt til \nHvornår: __timeValue__\nHvor: __card__\n forrige forfaldstidspunkt var __timeOldValue__", + "act-a-endAt": "ændrede sluttidspunkt til __timeValue__ fra (__timeOldValue__)", + "act-a-startAt": "ændrede starttidspunkt til __timeValue__ fra (__timeOldValue__)", + "act-a-receivedAt": "ændrede modtagelsestidspunkt til __timeValue__ fra (__timeOldValue__)", + "a-dueAt": "ændrede forfaldstidspunkt til at være", + "a-endAt": "ændrede sluttidspunkt til at være", + "a-startAt": "ændrede starttidspunkt til at være", + "a-receivedAt": "ændrede modtagelsestidspunkt til at være", + "almostdue": "aktuelt forfaldstidspunkt %s nærmer sig", + "pastdue": "aktuelt forfaldstidspunkt %s er passeret", + "duenow": "aktuelt forfaldstidspunkt %s er i dag", + "act-newDue": "__list__/__card__ har 1. påmindelse om forfald [__board__]", + "act-withDue": "__list__/__card__ påmindelse om forfald [__board__]", + "act-almostdue": "påmindede om at aktuelt forfald (__timeValue__) for __card__ nærmer sig", + "act-pastdue": "påmindede om at aktuelt forfald (__timeValue__) of __card__ er passeret", + "act-duenow": "påmindede om at aktuelt forfald (__timeValue__) of __card__ er nu", + "act-atUserComment": "Du blev nævnt i [__board__] __list__/__card__", + "delete-user-confirm-popup": "Er du sikker på du vil slette denne konto? Det er ikke muligt at fortryde.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Tillad brugere at slette deres egen konto", + "hide-minicard-label-text": "Skjul etiketteteksten for minikort", + "show-desktop-drag-handles": "Vis trække-håndtag for skrivebord", + "assignee": "Tildelt til", + "cardAssigneesPopup-title": "Tildelt til", + "addmore-detail": "Tilføj en mere detaljeret beskrivelse", + "show-on-card": "Vis på kort", + "new": "Ny", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Redigér bruger", + "newUserPopup-title": "Ny bruger", + "notifications": "Notifikationer", + "view-all": "Vis alle", + "filter-by-unread": "Filtrér efter ulæst", + "mark-all-as-read": "Markér alle som læst", + "remove-all-read": "Fjern alle læste", + "allow-rename": "Tillad omdøb", + "allowRenamePopup-title": "Tillad omdøb", + "start-day-of-week": "Angiv dag for ugestart", + "monday": "Mandag", + "tuesday": "Tirsdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lørdag", + "sunday": "Søndag", + "status": "Status", + "swimlane": "Svømmebaner", + "owner": "Ejer", + "last-modified-at": "Senest ændret per", + "last-activity": "Seneste aktivitet", + "voting": "Afstemning", + "archived": "Arkiveret", + "delete-linked-card-before-this-card": "Du kan ikke slette dette kort før der slettes sammenkædede kort som har", + "delete-linked-cards-before-this-list": "Du kan ikke slette denne liste før der slettes sammenkædede kort, der peger til kort i denne liste", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Kort", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "liste", + "operator-list-abbrev": "l", + "operator-label": "etikette", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "medlem", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "forfalder", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "forfalder", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "tjekliste", + "predicate-start": "start", + "predicate-end": "slutter", + "predicate-assignee": "assignee", + "predicate-member": "medlem", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Tal", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/de-CH.i18n.json b/i18n/de-CH.i18n.json new file mode 100644 index 000000000..f4ad5e955 --- /dev/null +++ b/i18n/de-CH.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Akzeptieren", + "act-activity-notify": "Aktivitätsbenachrichtigung", + "act-addAttachment": "hat Anhang __attachment__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-deleteAttachment": "hat Anhang __attachment__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", + "act-addSubtask": "hat Teilaufgabe __subtask__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addedLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-removeLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-removedLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-addChecklist": "hat Checkliste __checklist__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addChecklistItem": "hat Checklistenposition __checklistItem__ zu Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-removeChecklist": "hat Checkliste __checklist__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-removeChecklistItem": "hat Checklistenposition __checklistItem__ von Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-checkedItem": "hat __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ abgehakt", + "act-uncheckedItem": "hat Haken von __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-completeChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", + "act-uncompleteChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ unvervollständigt", + "act-addComment": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ kommentiert: __comment__", + "act-editComment": "hat den Kommentar auf Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", + "act-deleteComment": "hat den Kommentar von Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", + "act-createBoard": "hat Board __board__ erstellt", + "act-createSwimlane": "hat Swimlane __swimlane__ in Board __board__ erstellt", + "act-createCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ erstellt", + "act-createCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ angelegt", + "act-deleteCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ gelöscht", + "act-setCustomField": "hat benutzerdefiniertes Feld __customField__: __customFieldValue__ auf Karte __card__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", + "act-createList": "hat Liste __list__ zu Board __board__ hinzugefügt", + "act-addBoardMember": "hat Mitglied __member__ zu Board __board__ hinzugefügt", + "act-archivedBoard": "hat Board __board__ ins Archiv verschoben", + "act-archivedCard": "hat Karte __card__ von der Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", + "act-archivedList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", + "act-archivedSwimlane": "hat Swimlane __swimlane__ von Board __board__ ins Archiv verschoben", + "act-importBoard": "hat Board __board__ importiert", + "act-importCard": "hat Karte __card__ in Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", + "act-importList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", + "act-joinMember": "hat Mitglied __member__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-moveCard": "hat Karte __card__ in Board __board__ von Liste __oldList__ in Swimlane __oldSwimlane__ zu Liste __list__ in Swimlane __swimlane__ verschoben", + "act-moveCardToOtherBoard": "hat Karte __card__ von Liste __oldList__ in Swimlane __oldSwimlane__ in Board __oldBoard__ zu Liste __list__ in Swimlane __swimlane__ in Board __board__ verschoben", + "act-removeBoardMember": "hat Mitglied __member__ von Board __board__ entfernt", + "act-restoredCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ wiederhergestellt", + "act-unjoinMember": "hat Mitglied __member__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Aktionen", + "activities": "Aktivitäten", + "activity": "Aktivität", + "activity-added": "hat %s zu %s hinzugefügt", + "activity-archived": "hat %s ins Archiv verschoben", + "activity-attached": "hat %s an %s angehängt", + "activity-created": "hat %s erstellt", + "activity-customfield-created": "hat das benutzerdefinierte Feld %s erstellt", + "activity-excluded": "hat %s von %s ausgeschlossen", + "activity-imported": "hat %s in %s von %s importiert", + "activity-imported-board": "hat %s von %s importiert", + "activity-joined": "ist %s beigetreten", + "activity-moved": "hat %s von %s nach %s verschoben", + "activity-on": "in %s", + "activity-removed": "hat %s von %s entfernt", + "activity-sent": "hat %s an %s gesendet", + "activity-unjoined": "hat %s verlassen", + "activity-subtask-added": "Teilaufgabe zu %s hinzugefügt", + "activity-checked-item": "markierte %s in Checkliste %s von %s", + "activity-unchecked-item": "hat %s in Checkliste %s von %s abgewählt", + "activity-checklist-added": "hat eine Checkliste zu %s hinzugefügt", + "activity-checklist-removed": "entfernte eine Checkliste von %s", + "activity-checklist-completed": "Abgeschlossene Checkliste", + "activity-checklist-uncompleted": "unvervollständigte die Checkliste %s von %s", + "activity-checklist-item-added": "hat ein Checklistenelement zu '%s' in %s hinzugefügt", + "activity-checklist-item-removed": "hat ein Checklistenelement von '%s' in %s entfernt", + "add": "Hinzufügen", + "activity-checked-item-card": "markiere %s in Checkliste %s", + "activity-unchecked-item-card": "hat %s in Checkliste %s abgewählt", + "activity-checklist-completed-card": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", + "activity-checklist-uncompleted-card": "unvervollständigte die Checkliste %s", + "activity-editComment": "editierte Kommentar %s", + "activity-deleteComment": "löschte Kommentar %s", + "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", + "activity-startDate": "hat Startdatum zu %s geändert auf %s", + "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", + "activity-endDate": "hat Enddatum zu %s geändert auf %s", + "add-attachment": "Datei anhängen", + "add-board": "Board hinzufügen", + "add-template": "Vorlage hinzufügen", + "add-card": "Karte hinzufügen", + "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", + "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", + "add-swimlane": "Swimlane hinzufügen", + "add-subtask": "Teilaufgabe hinzufügen", + "add-checklist": "Checkliste hinzufügen", + "add-checklist-item": "Element zu Checkliste hinzufügen", + "add-cover": "Cover hinzufügen", + "add-label": "Label hinzufügen", + "add-list": "Liste hinzufügen", + "add-members": "Mitglieder hinzufügen", + "added": "Hinzugefügt", + "addMemberPopup-title": "Mitglieder", + "admin": "Admin", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", + "admin-announcement": "Ankündigung", + "admin-announcement-active": "Aktive systemweite Ankündigungen", + "admin-announcement-title": "Ankündigung des Administrators", + "all-boards": "Alle Boards", + "and-n-other-card": "und eine andere Karte", + "and-n-other-card_plural": "und __count__ andere Karten", + "apply": "Übernehmen", + "app-is-offline": "Laden, bitte warten. Das Aktualisieren der Seite führt zu Datenverlust. Wenn das Laden nicht funktioniert, überprüfen Sie bitte, ob der Server nicht angehalten wurde.", + "archive": "Ins Archiv verschieben", + "archive-all": "Alles ins Archiv verschieben", + "archive-board": "Board ins Archiv verschieben", + "archive-card": "Karte ins Archiv verschieben", + "archive-list": "Liste ins Archiv verschieben", + "archive-swimlane": "Swimlane ins Archiv verschieben", + "archive-selection": "Auswahl ins Archiv verschieben", + "archiveBoardPopup-title": "Board ins Archiv verschieben?", + "archived-items": "Archiv", + "archived-boards": "Boards im Archiv", + "restore-board": "Board wiederherstellen", + "no-archived-boards": "Keine Boards im Archiv.", + "archives": "Archiv", + "template": "Vorlage", + "templates": "Vorlagen", + "template-container": "Vorlagen-Container", + "add-template-container": "Vorlagen-Container hinzufügen", + "assign-member": "Mitglied zuweisen", + "attached": "angehängt", + "attachment": "Anhang", + "attachment-delete-pop": "Das Löschen eines Anhangs kann nicht rückgängig gemacht werden.", + "attachmentDeletePopup-title": "Anhang löschen?", + "attachments": "Anhänge", + "auto-watch": "Neue Boards nach Erstellung automatisch beobachten", + "avatar-too-big": "Das Profilbild ist zu gross (520KB max)", + "back": "Zurück", + "board-change-color": "Farbe ändern", + "board-nb-stars": "%s Sterne", + "board-not-found": "Board nicht gefunden", + "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", + "board-public-info": "Dieses Board wird <strong>öffentlich zugänglich</strong> sein.", + "board-drag-drop-reorder-or-click-open": "Ziehen und Fallenlassen um die Board-Icons neu anzuordnen. Ein Klick auf das Board-Icon öffnet das zugehörige Board.", + "boardChangeColorPopup-title": "Farbe des Boards ändern", + "boardChangeTitlePopup-title": "Board umbenennen", + "boardChangeVisibilityPopup-title": "Sichtbarkeit ändern", + "boardChangeWatchPopup-title": "Beobachtung ändern", + "boardMenuPopup-title": "Boardeinstellungen", + "boardChangeViewPopup-title": "Boardansicht", + "boards": "Boards", + "board-view": "Boardansicht", + "board-view-cal": "Kalender", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Einklappen", + "board-view-gantt": "Gantt", + "board-view-lists": "Listen", + "bucket-example": "z.B. \"Löffelliste\"", + "cancel": "Abbrechen", + "card-archived": "Diese Karte wurde ins Archiv verschoben", + "board-archived": "Dieses Board wurde ins Archiv verschoben.", + "card-comments-title": "Diese Karte hat %s Kommentar(e).", + "card-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Alle Aktionen, die dieser Karte zugeordnet sind, werden ebenfalls gelöscht.", + "card-delete-pop": "Alle Aktionen werden aus dem Aktivitätsfeed entfernt und die Karte kann nicht wiedereröffnet werden. Die Aktion kann nicht rückgängig gemacht werden.", + "card-delete-suggest-archive": "Sie können eine Karte ins Archiv verschieben, um sie vom Board zu entfernen und die Aktivitäten zu behalten.", + "card-due": "fällig", + "card-due-on": "fällig am", + "card-spent": "Aufgewendete Zeit", + "card-edit-attachments": "Anhänge ändern", + "card-edit-custom-fields": "Benutzerdefinierte Felder bearbeiten", + "card-edit-labels": "Labels ändern", + "card-edit-members": "Mitglieder ändern", + "card-labels-title": "Labels für diese Karte ändern.", + "card-members-title": "Der Karte Board-Mitglieder hinzufügen oder entfernen.", + "card-start": "Start", + "card-start-on": "Start am", + "cardAttachmentsPopup-title": "Anhängen von", + "cardCustomField-datePopup-title": "Datum ändern", + "cardCustomFieldsPopup-title": "Benutzerdefinierte Felder bearbeiten", + "cardStartVotingPopup-title": "Abstimmung starten", + "positiveVoteMembersPopup-title": "Befürworter", + "negativeVoteMembersPopup-title": "Gegner", + "card-edit-voting": "Abstimmung bearbeiten", + "editVoteEndDatePopup-title": "Enddatum der Abstimmung ändern", + "allowNonBoardMembers": "Alle eingeloggte Nutzer erlauben", + "vote-question": "Abstimmen über", + "vote-public": "Zeigen, wer was gewählt hat", + "vote-for-it": "Dafür", + "vote-against": "Dagegen", + "deleteVotePopup-title": "Wahl löschen?", + "vote-delete-pop": "Löschen ist unwiderruflich. Alle Aktionen die dieser Karte zugeordnet sind werden ebenfalls gelöscht.", + "cardStartPlanningPokerPopup-title": "Planning Poker starten", + "card-edit-planning-poker": "Planning Poker bearbeiten", + "editPokerEndDatePopup-title": "Ende-Datum dieses Planning Poker ändern", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Beenden", + "poker-result-votes": "Werte", + "poker-result-who": "Wer", + "poker-replay": "Wiederholen", + "set-estimation": "Schätzung vornehmen", + "deletePokerPopup-title": "Planning Poker löschen ?", + "poker-delete-pop": "Die Löschung ist permanent. Sie werden alles im Zusammenhang mit diesem Planning Poker verlieren.", + "cardDeletePopup-title": "Karte löschen?", + "cardDetailsActionsPopup-title": "Kartenaktionen", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Mitglieder", + "cardMorePopup-title": "Mehr", + "cardTemplatePopup-title": "Vorlage erstellen", + "cards": "Karten", + "cards-count": "Karten", + "cards-count-one": "Karte", + "casSignIn": "Mit CAS anmelden", + "cardType-card": "Karte", + "cardType-linkedCard": "Verknüpfte Karte", + "cardType-linkedBoard": "Verknüpftes Board", + "change": "Ändern", + "change-avatar": "Profilbild ändern", + "change-password": "Passwort ändern", + "change-permissions": "Berechtigungen ändern", + "change-settings": "Einstellungen ändern", + "changeAvatarPopup-title": "Profilbild ändern", + "changeLanguagePopup-title": "Sprache ändern", + "changePasswordPopup-title": "Passwort ändern", + "changePermissionsPopup-title": "Berechtigungen ändern", + "changeSettingsPopup-title": "Einstellungen ändern", + "subtasks": "Teilaufgaben", + "checklists": "Checklisten", + "click-to-star": "Klicken Sie, um das Board mit einem Stern zu markieren.", + "click-to-unstar": "Klicken Sie, um den Stern vom Board zu entfernen.", + "clipboard": "Zwischenablage oder Drag & Drop", + "close": "Schliessen", + "close-board": "Board schliessen", + "close-board-pop": "Sie können das Board wiederherstellen, indem Sie die Schaltfläche \"Archiv\" in der Kopfzeile der Startseite anklicken.", + "close-card": "Karte schliessen", + "color-black": "schwarz", + "color-blue": "blau", + "color-crimson": "Karminrot", + "color-darkgreen": "Dunkelgrün", + "color-gold": "Gold", + "color-gray": "Grau", + "color-green": "grün", + "color-indigo": "Indigo", + "color-lime": "hellgrün", + "color-magenta": "Magentarot", + "color-mistyrose": "Altrosa", + "color-navy": "Marineblau", + "color-orange": "orange", + "color-paleturquoise": "Blasses Türkis", + "color-peachpuff": "Pfirsich", + "color-pink": "pink", + "color-plum": "Pflaume", + "color-purple": "lila", + "color-red": "rot", + "color-saddlebrown": "Sattelbraun", + "color-silver": "Silber", + "color-sky": "himmelblau", + "color-slateblue": "Schieferblau", + "color-white": "Weiss", + "color-yellow": "gelb", + "unset-color": "Nicht festgelegt", + "comment": "Kommentar", + "comment-placeholder": "Kommentar schreiben", + "comment-only": "Nur Kommentare", + "comment-only-desc": "Kann Karten nur kommentieren.", + "no-comments": "Keine Kommentare", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", + "worker": "Arbeiter", + "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?", + "confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?", + "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", + "linkCardPopup-title": "Karte verknüpfen", + "searchElementPopup-title": "Suche", + "copyCardPopup-title": "Karte kopieren", + "copyChecklistToManyCardsPopup-title": "Checklisten-Vorlage in mehrere Karten kopieren", + "copyChecklistToManyCardsPopup-instructions": "Titel und Beschreibungen der Zielkarten im folgenden JSON-Format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", + "create": "Erstellen", + "createBoardPopup-title": "Board erstellen", + "chooseBoardSourcePopup-title": "Board importieren", + "createLabelPopup-title": "Label erstellen", + "createCustomField": "Feld erstellen", + "createCustomFieldPopup-title": "Feld erstellen", + "current": "aktuell", + "custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.", + "custom-field-checkbox": "Kontrollkästchen", + "custom-field-currency": "Währung", + "custom-field-currency-option": "Währungszeichen", + "custom-field-date": "Datum", + "custom-field-dropdown": "Dropdownliste", + "custom-field-dropdown-none": "(keiner)", + "custom-field-dropdown-options": "Listenoptionen", + "custom-field-dropdown-options-placeholder": "Drücken Sie die Eingabetaste, um weitere Optionen hinzuzufügen", + "custom-field-dropdown-unknown": "(unbekannt)", + "custom-field-number": "Zahl", + "custom-field-text": "Text", + "custom-fields": "Benutzerdefinierte Felder", + "date": "Datum", + "decline": "Ablehnen", + "default-avatar": "Standard Profilbild", + "delete": "Löschen", + "deleteCustomFieldPopup-title": "Benutzerdefiniertes Feld löschen?", + "deleteLabelPopup-title": "Label löschen?", + "description": "Beschreibung", + "disambiguateMultiLabelPopup-title": "Labels vereinheitlichen", + "disambiguateMultiMemberPopup-title": "Mitglieder vereinheitlichen", + "discard": "Verwerfen", + "done": "Erledigt", + "download": "Herunterladen", + "edit": "Bearbeiten", + "edit-avatar": "Profilbild ändern", + "edit-profile": "Profil ändern", + "edit-wip-limit": "WIP-Limit bearbeiten", + "soft-wip-limit": "Soft WIP-Limit", + "editCardStartDatePopup-title": "Startdatum ändern", + "editCardDueDatePopup-title": "Fälligkeitsdatum ändern", + "editCustomFieldPopup-title": "Feld bearbeiten", + "editCardSpentTimePopup-title": "Aufgewendete Zeit ändern", + "editLabelPopup-title": "Label ändern", + "editNotificationPopup-title": "Benachrichtigung ändern", + "editProfilePopup-title": "Profil ändern", + "email": "E-Mail", + "email-enrollAccount-subject": "Ihr Benutzerkonto auf __siteName__ wurde erstellt", + "email-enrollAccount-text": "Hallo __user__,\n\num den Dienst nutzen zu können, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "email-fail": "Senden der E-Mail fehlgeschlagen", + "email-fail-text": "Fehler beim Senden der E-Mail", + "email-invalid": "Ungültige E-Mail-Adresse", + "email-invite": "per E-Mail einladen", + "email-invite-subject": "__inviter__ hat Ihnen eine Einladung geschickt", + "email-invite-text": "Hallo __user__,\n\n__inviter__ hat Sie zu dem Board \"__board__\" eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n\n__url__\n\nDanke.", + "email-resetPassword-subject": "Setzten Sie ihr Passwort auf __siteName__ zurück", + "email-resetPassword-text": "Hallo __user__,\n\num ihr Passwort zurückzusetzen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "email-sent": "E-Mail gesendet", + "email-verifyEmail-subject": "Bestätigen Sie ihre E-Mail-Adresse auf __siteName__", + "email-verifyEmail-text": "Hallo __user__,\n\num ihre E-Mail-Adresse zu bestätigen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "enable-wip-limit": "WIP-Limit einschalten", + "error-board-doesNotExist": "Dieses Board existiert nicht", + "error-board-notAdmin": "Um das zu tun, müssen Sie Administrator dieses Boards sein", + "error-board-notAMember": "Um das zu tun, müssen Sie Mitglied dieses Boards sein", + "error-json-malformed": "Ihre Eingabe ist kein gültiges JSON", + "error-json-schema": "Ihre JSON-Daten enthalten nicht die gewünschten Informationen im richtigen Format", + "error-csv-schema": "hre CSV (Comma Separated Values)/TSV (Tab Separated Values) enthalten nicht die gewünschten Informationen im richtigen Format", + "error-list-doesNotExist": "Diese Liste existiert nicht", + "error-user-doesNotExist": "Dieser Nutzer existiert nicht", + "error-user-notAllowSelf": "Sie können sich nicht selbst einladen.", + "error-user-notCreated": "Dieser Nutzer ist nicht angelegt", + "error-username-taken": "Dieser Benutzername ist bereits vergeben", + "error-orgname-taken": "Dieser Organisationsname ist schon vergeben", + "error-teamname-taken": "Dieser Teamname ist schon vergeben", + "error-email-taken": "E-Mail wird schon verwendet", + "export-board": "Board exportieren", + "export-board-json": "Board als JSON exportieren", + "export-board-csv": "Board als CSV exportieren", + "export-board-tsv": "Board als TSV exportieren", + "export-board-excel": "Board nach Excel exportieren", + "user-can-not-export-excel": "Benutzer kann nicht nach Excel exportieren", + "export-board-html": "Board als HTML exportieren", + "export-card": "Karte exportieren", + "export-card-pdf": "Karte als PDF exportieren", + "user-can-not-export-card-to-pdf": "Der Benutzer kann die Karte nicht als PDF exportieren", + "exportBoardPopup-title": "Board exportieren", + "exportCardPopup-title": "Karte exportieren", + "sort": "Sortieren", + "sort-desc": "Zum Sortieren der Liste klicken", + "list-sort-by": "Sortieren der Liste nach:", + "list-label-modifiedAt": "Letzte Zugriffszeit", + "list-label-title": "Name der Liste", + "list-label-sort": "Ihre manuelle Sortierung", + "list-label-short-modifiedAt": "(Z)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Karten oder Listen filtern", + "filter-dates-label": "nach Datum", + "filter-no-due-date": "ohne Fälligkeitsdatum", + "filter-overdue": "überfällig", + "filter-due-today": "heute fällig", + "filter-due-this-week": "diese Woche fällig", + "filter-due-tomorrow": "morgen fällig", + "list-filter-label": "Liste nach Titel filtern", + "filter-clear": "Filter entfernen", + "filter-labels-label": "Nach Label filtern", + "filter-no-label": "Kein Label", + "filter-member-label": "Nach Nutzer filtern", + "filter-no-member": "Kein Mitglied", + "filter-assignee-label": "Nach Zuordnung filtern", + "filter-no-assignee": "Nicht zugewiesen", + "filter-custom-fields-label": "Filtern nach benutzerdefinierten Feldern", + "filter-no-custom-fields": "Keine benutzerdefinierten Felder", + "filter-show-archive": "Archivierte Listen anzeigen", + "filter-hide-empty": "Leere Listen verstecken", + "filter-on": "Filter ist aktiv", + "filter-on-desc": "Sie filtern die Karten in diesem Board. Klicken Sie, um den Filter zu bearbeiten.", + "filter-to-selection": "Ergebnisse auswählen", + "other-filters-label": "Andere Filter", + "advanced-filter-label": "Erweiterter Filter", + "advanced-filter-description": "Der erweiterte Filter erlaubt die Eingabe von Zeichenfolgen, die folgende Operatoren enthalten: == != <= >= && || ( ). Ein Leerzeichen wird als Trennzeichen zwischen den Operatoren verwendet. Sie können nach allen benutzerdefinierten Feldern filtern, indem Sie deren Namen und Werte eingeben. Zum Beispiel: Feld1 == Wert1. Hinweis: Wenn Felder oder Werte Leerzeichen enthalten, müssen Sie sie in einfache Anführungszeichen setzen. Zum Beispiel: 'Feld 1' == 'Wert 1'. Um einzelne Steuerzeichen (' \\/) zu überspringen, können Sie \\ verwenden. Zum Beispiel: Feld1 == Ich bin\\'s. Sie können ausserdem mehrere Bedingungen kombinieren. Zum Beispiel: F1 == W1 || F1 == W2. Normalerweise werden alle Operatoren von links nach rechts interpretiert. Sie können die Reihenfolge ändern, indem Sie Klammern setzen. Zum Beispiel: F1 == W1 && ( F2 == W2 || F2 == W3 ). Sie können Textfelder auch mithilfe regulärer Ausdrücke durchsuchen: F1 == /Tes.*/i", + "fullname": "Vollständiger Name", + "header-logo-title": "Zurück zur Board Seite.", + "hide-system-messages": "Systemmeldungen ausblenden", + "headerBarCreateBoardPopup-title": "Board erstellen", + "home": "Home", + "import": "Importieren", + "impersonate-user": "als Benutzer ausgeben", + "link": "Verknüpfung", + "import-board": "Board importieren", + "import-board-c": "Board importieren", + "import-board-title-trello": "Board von Trello importieren", + "import-board-title-wekan": "Board aus vorherigem Export importieren", + "import-board-title-csv": "Board von CSV/TSV importieren", + "from-trello": "Von Trello", + "from-wekan": "Aus vorherigem Export", + "from-csv": "Aus CSV/TSV", + "import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text", + "import-board-instruction-csv": "Fügen Sie die Ihre Comma-Separated- (CSV) / bzw. Tab-Separated-Values (TSV) ein.", + "import-board-instruction-wekan": "Gehen Sie in Ihrem Board auf 'Menü', danach auf 'Board exportieren' und kopieren Sie den Text aus der heruntergeladenen Datei.", + "import-board-instruction-about-errors": "Treten beim importieren eines Board Fehler auf, so kann der Import dennoch erfolgreich abgeschlossen sein und das Board ist auf der Seite \"Alle Boards\" zusehen.", + "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein", + "import-csv-placeholder": "Fügen Sie die korrekten CSV/TSV-Daten hier ein", + "import-map-members": "Mitglieder zuordnen", + "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen sie die Mitglieder, die Sie importieren wollen, Ihren Benutzern zu.", + "import-members-map-note": "Anmerkung: Nicht zugeordnete Mitglieder werden dem aktuellen Benutzer zugeordnet.", + "import-show-user-mapping": "Mitgliederzuordnung überprüfen", + "import-user-select": "Wählen Sie den bestehenden Benutzer aus, den Sie für dieses Mitglied verwenden wollen.", + "importMapMembersAddPopup-title": "Mitglied auswählen", + "info": "Version", + "initials": "Initialen", + "invalid-date": "Ungültiges Datum", + "invalid-time": "Ungültige Zeitangabe", + "invalid-user": "Ungültiger Benutzer", + "joined": "beigetreten", + "just-invited": "Sie wurden soeben zu diesem Board eingeladen", + "keyboard-shortcuts": "Tastaturkürzel", + "label-create": "Label erstellen", + "label-default": "%s Label (Standard)", + "label-delete-pop": "Aktion kann nicht rückgängig gemacht werden. Das Label wird von allen Karten entfernt und seine Historie gelöscht.", + "labels": "Labels", + "language": "Sprache", + "last-admin-desc": "Sie können keine Rollen ändern, weil es mindestens einen Administrator geben muss.", + "leave-board": "Board verlassen", + "leave-board-pop": "Sind Sie sicher, dass Sie __boardTitle__ verlassen möchten? Sie werden von allen Karten in diesem Board entfernt.", + "leaveBoardPopup-title": "Board verlassen?", + "link-card": "Link zu dieser Karte", + "list-archive-cards": "Alle Karten dieser Liste ins Archiv verschieben", + "list-archive-cards-pop": "Alle Karten dieser Liste werden vom Board entfernt. Um Karten im Papierkorb anzuzeigen und wiederherzustellen, klicken Sie auf \"Menü\" > \"Archiv\".", + "list-move-cards": "Alle Karten in dieser Liste verschieben", + "list-select-cards": "Alle Karten in dieser Liste auswählen", + "set-color-list": "Lege Farbe fest", + "listActionPopup-title": "Listenaktionen", + "settingsUserPopup-title": "Benutzereinstellungen", + "settingsTeamPopup-title": "Team-Einstellungen", + "settingsOrgPopup-title": "Organisations-Einstellungen", + "swimlaneActionPopup-title": "Swimlaneaktionen", + "swimlaneAddPopup-title": "Swimlane unterhalb einfügen", + "listImportCardPopup-title": "Eine Trello-Karte importieren", + "listImportCardsTsvPopup-title": "CSV/TSV importieren", + "listMorePopup-title": "Mehr", + "link-list": "Link zu dieser Liste", + "list-delete-pop": "Alle Aktionen werden aus dem Verlauf gelöscht und die Liste kann nicht wiederhergestellt werden.", + "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", + "lists": "Listen", + "swimlanes": "Swimlanes", + "log-out": "Ausloggen", + "log-in": "Einloggen", + "loginPopup-title": "Einloggen", + "memberMenuPopup-title": "Nutzereinstellungen", + "members": "Mitglieder", + "menu": "Menü", + "move-selection": "Auswahl verschieben", + "moveCardPopup-title": "Karte verschieben", + "moveCardToBottom-title": "Ans Ende verschieben", + "moveCardToTop-title": "Zum Anfang verschieben", + "moveSelectionPopup-title": "Auswahl verschieben", + "multi-selection": "Mehrfachauswahl", + "multi-selection-label": "Label für die Auswahl setzen", + "multi-selection-member": "Mitglied für die Auswahl setzen", + "multi-selection-on": "Mehrfachauswahl ist aktiv", + "muted": "Stumm", + "muted-info": "Sie werden nicht über Änderungen auf diesem Board benachrichtigt", + "my-boards": "Meine Boards", + "name": "Name", + "no-archived-cards": "Keine Karten im Archiv.", + "no-archived-lists": "Keine Listen im Archiv.", + "no-archived-swimlanes": "Keine Swimlanes im Archiv.", + "no-results": "Keine Ergebnisse", + "normal": "Normal", + "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", + "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", + "notify-participate": "Benachrichtigungen zu allen Karten erhalten, an denen Sie teilnehmen", + "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", + "optional": "optional", + "or": "oder", + "page-maybe-private": "Diese Seite könnte privat sein. Vielleicht können Sie sie sehen, wenn Sie sich <a href='%s'>einloggen</a>.", + "page-not-found": "Seite nicht gefunden.", + "password": "Passwort", + "paste-or-dragdrop": "Einfügen oder Datei mit Drag & Drop ablegen (nur Bilder)", + "participating": "Teilnehmen", + "preview": "Vorschau", + "previewAttachedImagePopup-title": "Vorschau", + "previewClipboardImagePopup-title": "Vorschau", + "private": "Privat", + "private-desc": "Dieses Board ist privat. Nur Nutzer, die zu dem Board gehören, können es anschauen und bearbeiten.", + "profile": "Profil", + "public": "Öffentlich", + "public-desc": "Dieses Board ist öffentlich zugänglich. Es ist für jeden, der den Link kennt, sichtbar und taucht in Suchmaschinen wie Google auf. Nur Nutzer, die zum Board hinzugefügt wurden, können es bearbeiten.", + "quick-access-description": "Markieren Sie ein Board mit einem Stern, um dieser Leiste eine Verknüpfung hinzuzufügen.", + "remove-cover": "Cover entfernen", + "remove-from-board": "Von Board entfernen", + "remove-label": "Label entfernen", + "listDeletePopup-title": "Liste löschen?", + "remove-member": "Nutzer entfernen", + "remove-member-from-card": "Von Karte entfernen", + "remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.", + "removeMemberPopup-title": "Mitglied entfernen?", + "rename": "Umbenennen", + "rename-board": "Board umbenennen", + "restore": "Wiederherstellen", + "save": "Speichern", + "search": "Suchen", + "rules": "Regeln", + "search-cards": "Suche nach Karten-/Listentiteln, Beschreibungen und personalisierten Feldern auf diesem Brett", + "search-example": "Suchtext eingeben und Enter drücken", + "select-color": "Farbe auswählen", + "select-board": "Board auswählen", + "set-wip-limit-value": "Setzen Sie ein Limit für die maximale Anzahl von Aufgaben in dieser Liste", + "setWipLimitPopup-title": "WIP-Limit setzen", + "shortcut-assign-self": "Fügen Sie sich zur aktuellen Karte hinzu", + "shortcut-autocomplete-emoji": "Emojis vervollständigen", + "shortcut-autocomplete-members": "Mitglieder vervollständigen", + "shortcut-clear-filters": "Alle Filter entfernen", + "shortcut-close-dialog": "Dialog schliessen", + "shortcut-filter-my-cards": "Meine Karten filtern", + "shortcut-show-shortcuts": "Liste der Tastaturkürzel anzeigen", + "shortcut-toggle-filterbar": "Filter-Seitenleiste ein-/ausblenden", + "shortcut-toggle-searchbar": "Such-Seitenleiste ein-/ausblenden", + "shortcut-toggle-sidebar": "Seitenleiste ein-/ausblenden", + "show-cards-minimum-count": "Zeigt die Kartenanzahl an, wenn die Liste mehr enthält als", + "sidebar-open": "Seitenleiste öffnen", + "sidebar-close": "Seitenleiste schliessen", + "signupPopup-title": "Benutzerkonto erstellen", + "star-board-title": "Klicken Sie, um das Board mit einem Stern zu markieren. Es erscheint dann oben in ihrer Boardliste.", + "starred-boards": "Markierte Boards", + "starred-boards-description": "Markierte Boards erscheinen oben in ihrer Boardliste.", + "subscribe": "Abonnieren", + "team": "Team", + "this-board": "diesem Board", + "this-card": "diese Karte", + "spent-time-hours": "Aufgewendete Zeit (Stunden)", + "overtime-hours": "Mehrarbeit (Stunden)", + "overtime": "Mehrarbeit", + "has-overtime-cards": "Hat Karten mit Mehrarbeit", + "has-spenttime-cards": "Hat Karten mit aufgewendeten Zeiten", + "time": "Zeit", + "title": "Titel", + "tracking": "Folgen", + "tracking-info": "Sie werden über alle Änderungen an Karten benachrichtigt, an denen Sie als Ersteller oder Mitglied beteiligt sind.", + "type": "Typ", + "unassign-member": "Mitglied entfernen", + "unsaved-description": "Sie haben eine nicht gespeicherte Änderung.", + "unwatch": "Beobachtung entfernen", + "upload": "Upload", + "upload-avatar": "Profilbild hochladen", + "uploaded-avatar": "Profilbild hochgeladen", + "custom-top-left-corner-logo-image-url": "Benutzerdefiniertes Logo oben links Bild URL", + "custom-top-left-corner-logo-link-url": "Benutzerdefiniertes Logo oben links Link URL", + "custom-top-left-corner-logo-height": "Benutzerdefiniertes Logo oben links Höhe. Voreinstellung: 27", + "custom-login-logo-image-url": "Benutzerdefiniertes Login Logo Bild URL", + "custom-login-logo-link-url": "Benutzerdefiniertes Login Logo Link URL", + "text-below-custom-login-logo": "Text unterhalb benutzerdefiniertem Login Logo", + "automatic-linked-url-schemes": "Spezielle URL-Schemas, die durch Klick automatisch öffenbar sein sollen. Ein URL-Schema pro Zeile", + "username": "Benutzername", + "import-usernames": "Nutzernamen importieren", + "view-it": "Ansehen", + "warn-list-archived": "Warnung: Diese Karte befindet sich in einer Liste im Archiv", + "watch": "Beobachten", + "watching": "Beobachten", + "watching-info": "Sie werden über alle Änderungen in diesem Board benachrichtigt", + "welcome-board": "Willkommen-Board", + "welcome-swimlane": "Meilenstein 1", + "welcome-list1": "Grundlagen", + "welcome-list2": "Fortgeschritten", + "card-templates-swimlane": "Karten-Vorlagen", + "list-templates-swimlane": "Listen-Vorlagen", + "board-templates-swimlane": "Board-Vorlagen", + "what-to-do": "Was wollen Sie tun?", + "wipLimitErrorPopup-title": "Ungültiges WIP-Limit", + "wipLimitErrorPopup-dialog-pt1": "Die Anzahl von Aufgaben in dieser Liste ist grösser als das von Ihnen definierte WIP-Limit.", + "wipLimitErrorPopup-dialog-pt2": "Bitte verschieben Sie einige Aufgaben aus dieser Liste oder setzen Sie ein grösseres WIP-Limit.", + "admin-panel": "Administration", + "settings": "Einstellungen", + "people": "Nutzer", + "registration": "Registrierung", + "disable-self-registration": "Selbstregistrierung deaktivieren", + "invite": "Einladen", + "invite-people": "Nutzer einladen", + "to-boards": "In Board(s)", + "email-addresses": "E-Mail Adressen", + "smtp-host-description": "Die Adresse Ihres SMTP-Servers für ausgehende E-Mails.", + "smtp-port-description": "Der Port Ihres SMTP-Servers für ausgehende E-Mails.", + "smtp-tls-description": "Aktiviere TLS Unterstützung für SMTP Server", + "smtp-host": "SMTP-Server", + "smtp-port": "SMTP-Port", + "smtp-username": "Benutzername", + "smtp-password": "Passwort", + "smtp-tls": "TLS Unterstützung", + "send-from": "Absender", + "send-smtp-test": "Test-E-Mail an sich selbst schicken", + "invitation-code": "Einladungscode", + "email-invite-register-subject": "__inviter__ hat Ihnen eine Einladung geschickt", + "email-invite-register-text": "Sehr geehrte(r) __user__,\n\n__inviter__ hat Sie zur Mitarbeit an einem Kanbanboard eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n__url__\n\nIhr Einladungscode lautet: __icode__\n\nDanke.", + "email-smtp-test-subject": "SMTP Test-E-Mail", + "email-smtp-test-text": "Sie haben erfolgreich eine E-Mail versandt", + "error-invitation-code-not-exist": "Ungültiger Einladungscode", + "error-notAuthorized": "Sie sind nicht berechtigt diese Seite zu sehen.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional für Authentifizierung)", + "outgoing-webhooks": "Ausgehende Webhooks", + "bidirectional-webhooks": "Zwei-Wege Webhooks", + "outgoingWebhooksPopup-title": "Ausgehende Webhooks", + "boardCardTitlePopup-title": "Kartentitelfilter", + "disable-webhook": "Diesen Webhook deaktivieren", + "global-webhook": "Globale Webhooks", + "new-outgoing-webhook": "Neuer ausgehender Webhook", + "no-name": "(Unbekannt)", + "Node_version": "Node-Version", + "Meteor_version": "Meteor-Version", + "MongoDB_version": "MongoDB-Version", + "MongoDB_storage_engine": "MongoDB-Speicher-Engine", + "MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert", + "OS_Arch": "Betriebssystem-Architektur", + "OS_Cpus": "Anzahl Prozessoren", + "OS_Freemem": "Freier Arbeitsspeicher", + "OS_Loadavg": "Mittlere Systembelastung", + "OS_Platform": "Plattform", + "OS_Release": "Version des Betriebssystem", + "OS_Totalmem": "Gesamter Arbeitsspeicher", + "OS_Type": "Typ des Betriebssystems", + "OS_Uptime": "Laufzeit des Systems", + "days": "Tage", + "hours": "Stunden", + "minutes": "Minuten", + "seconds": "Sekunden", + "show-field-on-card": "Zeige dieses Feld auf der Karte", + "automatically-field-on-card": "Füge Feld neuen Karten hinzu", + "always-field-on-card": "Füge Feld allen Karten hinzu", + "showLabel-field-on-card": "Feldbezeichnung auf Minikarte anzeigen", + "yes": "Ja", + "no": "Nein", + "accounts": "Konten", + "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", + "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", + "createdAt": "Erstellt am", + "modifiedAt": "Geändert am", + "verified": "Geprüft", + "active": "Aktiv", + "card-received": "Empfangen", + "card-received-on": "Empfangen am", + "card-end": "Ende", + "card-end-on": "Endet am", + "editCardReceivedDatePopup-title": "Empfangsdatum ändern", + "editCardEndDatePopup-title": "Enddatum ändern", + "setCardColorPopup-title": "Farbe festlegen", + "setCardActionsColorPopup-title": "Farbe wählen", + "setSwimlaneColorPopup-title": "Farbe wählen", + "setListColorPopup-title": "Farbe wählen", + "assigned-by": "Zugewiesen von", + "requested-by": "Angefordert von", + "card-sorting-by-number": "Karten nach Nummer sortieren", + "board-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Sie werden alle Listen, Karten und Aktionen, die mit diesem Board verbunden sind, verlieren.", + "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", + "boardDeletePopup-title": "Board löschen?", + "delete-board": "Board löschen", + "default-subtasks-board": "Teilaufgabe für __board__ Board", + "default": "Standard", + "queue": "Warteschlange", + "subtask-settings": "Einstellungen für Teilaufgaben", + "card-settings": "Karten-Einstellungen", + "boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben", + "boardCardSettingsPopup-title": "Karten-Einstellungen", + "deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:", + "deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:", + "show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:", + "prefix-with-full-path": "Vollständiger Pfad über Titel", + "prefix-with-parent": "Über Titel", + "subtext-with-full-path": "Vollständiger Pfad unter Titel", + "subtext-with-parent": "Unter Titel", + "change-card-parent": "Übergeordnete Karte ändern", + "parent-card": "Übergeordnete Karte", + "source-board": "Quellboard", + "no-parent": "Nicht anzeigen", + "activity-added-label": "fügte Label '%s' zu %s hinzu", + "activity-removed-label": "entfernte Label '%s' von %s", + "activity-delete-attach": "löschte ein Anhang von %s", + "activity-added-label-card": "Label hinzugefügt '%s'", + "activity-removed-label-card": "Label entfernt '%s'", + "activity-delete-attach-card": "hat einen Anhang gelöscht", + "activity-set-customfield": "setze benutzerdefiniertes Feld '%s' zu '%s' in %s", + "activity-unset-customfield": "entferne benutzerdefiniertes Feld '%s' in %s", + "r-rule": "Regel", + "r-add-trigger": "Auslöser hinzufügen", + "r-add-action": "Aktion hinzufügen", + "r-board-rules": "Boardregeln", + "r-add-rule": "Regel hinzufügen", + "r-view-rule": "Regel anzeigen", + "r-delete-rule": "Regel löschen", + "r-new-rule-name": "Neuer Regeltitel", + "r-no-rules": "Keine Regeln", + "r-trigger": "Auslöser", + "r-action": "Aktion", + "r-when-a-card": "Wenn Karte", + "r-is": "wird", + "r-is-moved": "verschoben wird", + "r-added-to": "Hinzugefügt zu", + "r-removed-from": "entfernt von", + "r-the-board": "das Board", + "r-list": "Liste", + "list": "Liste", + "set-filter": "Setze Filter", + "r-moved-to": "verschoben nach", + "r-moved-from": "verschoben von", + "r-archived": "ins Archiv verschoben", + "r-unarchived": "aus dem Archiv wiederhergestellt", + "r-a-card": "einer Karte", + "r-when-a-label-is": "Wenn ein Label", + "r-when-the-label": "Wenn das Label", + "r-list-name": "Listenname", + "r-when-a-member": "Wenn ein Mitglied", + "r-when-the-member": "Wenn das Mitglied", + "r-name": "Name", + "r-when-a-attach": "Wenn ein Anhang", + "r-when-a-checklist": "Wenn eine Checkliste wird", + "r-when-the-checklist": "Wenn die Checkliste", + "r-completed": "abgeschlossen", + "r-made-incomplete": "unvollständig gemacht", + "r-when-a-item": "Wenn eine Checklistenposition", + "r-when-the-item": "Wenn die Checklistenposition", + "r-checked": "markiert wird", + "r-unchecked": "abgewählt wird", + "r-move-card-to": "Verschiebe Karte an", + "r-top-of": "Anfang von", + "r-bottom-of": "Ende von", + "r-its-list": "seiner Liste", + "r-archive": "Ins Archiv verschieben", + "r-unarchive": "Aus dem Archiv wiederherstellen", + "r-card": "Karte", + "r-add": "Hinzufügen", + "r-remove": "entfernen", + "r-label": "Label", + "r-member": "Mitglied", + "r-remove-all": "Entferne alle Mitglieder von der Karte", + "r-set-color": "Farbe festlegen auf", + "r-checklist": "Checkliste", + "r-check-all": "Alle markieren", + "r-uncheck-all": "Alle abwählen", + "r-items-check": "Elemente der Checkliste", + "r-check": "Markieren", + "r-uncheck": "Abwählen", + "r-item": "Element", + "r-of-checklist": "der Checkliste", + "r-send-email": "Eine E-Mail senden", + "r-to": "an", + "r-of": "von", + "r-subject": "Betreff", + "r-rule-details": "Regeldetails", + "r-d-move-to-top-gen": "Karte nach oben in die Liste verschieben", + "r-d-move-to-top-spec": "Karte an den Anfang der Liste verschieben", + "r-d-move-to-bottom-gen": "Karte nach unten in die Liste verschieben", + "r-d-move-to-bottom-spec": "Karte an das Ende der Liste verschieben", + "r-d-send-email": "E-Mail senden", + "r-d-send-email-to": "an", + "r-d-send-email-subject": "Betreff", + "r-d-send-email-message": "Nachricht", + "r-d-archive": "Karte ins Archiv verschieben", + "r-d-unarchive": "Karte aus dem Archiv wiederherstellen", + "r-d-add-label": "Label hinzufügen", + "r-d-remove-label": "Label entfernen", + "r-create-card": "Neue Karte erstellen", + "r-in-list": "in der Liste", + "r-in-swimlane": "in Swimlane", + "r-d-add-member": "Mitglied hinzufügen", + "r-d-remove-member": "Mitglied entfernen", + "r-d-remove-all-member": "Entferne alle Mitglieder", + "r-d-check-all": "Alle Elemente der Liste markieren", + "r-d-uncheck-all": "Alle Element der Liste abwählen", + "r-d-check-one": "Element auswählen", + "r-d-uncheck-one": "Element abwählen", + "r-d-check-of-list": "der Checkliste", + "r-d-add-checklist": "Checkliste hinzufügen", + "r-d-remove-checklist": "Checkliste entfernen", + "r-by": "durch", + "r-add-checklist": "Checkliste hinzufügen", + "r-with-items": "mit Elementen", + "r-items-list": "Element1,Element2,Element3", + "r-add-swimlane": "Füge Swimlane hinzu", + "r-swimlane-name": "Swimlanename", + "r-board-note": "Hinweis: Lassen Sie ein Feld leer, um alle möglichen Werte zu finden.", + "r-checklist-note": "Hinweis: Die Elemente der Checkliste müssen als kommagetrennte Werte geschrieben werden.", + "r-when-a-card-is-moved": "Wenn eine Karte in eine andere Liste verschoben wird", + "r-set": "Setze", + "r-update": "Aktualisiere", + "r-datefield": "Datumsfeld", + "r-df-start-at": "Start", + "r-df-due-at": "fällig", + "r-df-end-at": "Ende", + "r-df-received-at": "Empfangen", + "r-to-current-datetime": "auf das aktuelle Datum/Zeit", + "r-remove-value-from": "Entferne Wert von", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentifizierungsmethode", + "authentication-type": "Authentifizierungstyp", + "custom-product-name": "Benutzerdefinierter Produktname", + "layout": "Layout", + "hide-logo": "Verstecke Logo", + "add-custom-html-after-body-start": "Füge benutzerdefiniertes HTML nach <body> Anfang hinzu", + "add-custom-html-before-body-end": "Füge benutzerdefiniertes HTML vor </body>Ende hinzu", + "error-undefined": "Etwas ist schief gelaufen", + "error-ldap-login": "Es ist ein Fehler beim Anmelden aufgetreten", + "display-authentication-method": "Anzeige Authentifizierungsverfahren", + "default-authentication-method": "Standardauthentifizierungsverfahren", + "duplicate-board": "Board duplizieren", + "org-number": "Die Anzahl an Organisationen ist:", + "team-number": "Die Anzahl an Teams ist:", + "people-number": "Anzahl der Personen:", + "swimlaneDeletePopup-title": "Swimlane löschen?", + "swimlane-delete-pop": "Alle Aktionen werden aus dem Aktivitätenfeed entfernt und die Swimlane kann nicht wiederhergestellt werden. Die Aktion kann nicht rückgängig gemacht werden.", + "restore-all": "Alles wiederherstellen", + "delete-all": "Alles löschen", + "loading": "Laden, bitte warten.", + "previous_as": "letzter Zeitpunkt war", + "act-a-dueAt": "hat Fälligkeit geändert auf\nWann: __timeValue__\nWo: __card__\nvorheriger Fälligkeitszeitpunkt war __timeOldValue__", + "act-a-endAt": "hat Ende auf __timeValue__ von (__timeOldValue__) geändert", + "act-a-startAt": "hat Start auf __timeValue__ von (__timeOldValue__) geändert", + "act-a-receivedAt": "hat Empfangszeit auf __timeValue__ von (__timeOldValue__) geändert", + "a-dueAt": "hat Fälligkeit geändert auf", + "a-endAt": "hat Ende geändert auf", + "a-startAt": "hat Startzeit geändert auf", + "a-receivedAt": "hat Empfangszeit geändert auf", + "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", + "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", + "duenow": "aktuelles Fälligkeitsdatum %s heute", + "act-newDue": "__list__/__card__ hat seine 1. fällige Erinnerung [__board__]", + "act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]", + "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", + "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", + "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", + "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.", + "delete-team-confirm-popup": "Sind Sie sicher, dass Sie dieses Team löschen möchten? Es gibt kein Zurück!", + "delete-org-confirm-popup": "Sind Sie sicher, dass Sie diese Organisation löschen möchten? Es gibt kein Zurück!", + "accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen", + "hide-minicard-label-text": "Labeltext auf Minikarte ausblenden", + "show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen", + "assignee": "Zugewiesen", + "cardAssigneesPopup-title": "Zugewiesen", + "addmore-detail": "Eine detailliertere Beschreibung hinzufügen", + "show-on-card": "Zeige auf Karte", + "new": "Neu", + "editOrgPopup-title": "Organisation bearbeiten", + "newOrgPopup-title": "Neue Organisation", + "editTeamPopup-title": "Team bearbeiten", + "newTeamPopup-title": "Neues Team", + "editUserPopup-title": "Benutzer ändern", + "newUserPopup-title": "Neuer Benutzer", + "notifications": "Benachrichtigungen", + "view-all": "Alle anzeigen", + "filter-by-unread": "Nur ungelesene", + "mark-all-as-read": "Alle als gelesen markieren", + "remove-all-read": "Alle gelesenen entfernen", + "allow-rename": "Umbenennen erlauben", + "allowRenamePopup-title": "Umbenennen erlauben", + "start-day-of-week": "Wochentagbeginn festlegen", + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag", + "sunday": "Sonntag", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Eigentümer", + "last-modified-at": "Zuletzt geändert um", + "last-activity": "Letzte Aktivität", + "voting": "Abstimunng", + "archived": "Archiviert", + "delete-linked-card-before-this-card": "Sie können diese Karte nicht löschen, bevor verbundene Karten nicht gelöscht wurden.", + "delete-linked-cards-before-this-list": "Sie können diese Liste erst löschen, wenn Sie alle Karten gelöscht haben, die auf Karten in dieser Liste verweisen.", + "hide-checked-items": "Erledigte ausblenden", + "task": "Aufgabe", + "create-task": "Aufgabe erstellen", + "ok": "OK", + "organizations": "Organisationen", + "teams": "Teams", + "displayName": "Anzeigename", + "shortName": "Kurzname", + "website": "Webseite", + "person": "Person", + "my-cards": "Meine Karten", + "card": "Karte", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "Meine Karten Sortierung", + "myCardsSortChangePopup-title": "Meine Karten Sortierung", + "myCardsSortChange-choice-board": "nach Board", + "myCardsSortChange-choice-dueat": "nach Fälligkeitsdatum", + "dueCards-title": "Fällige Karten", + "dueCardsViewChange-title": "Fällige Karten Ansicht", + "dueCardsViewChangePopup-title": "Fällige Karten Ansicht", + "dueCardsViewChange-choice-me": "Ich", + "dueCardsViewChange-choice-all": "alle Benutzer", + "dueCardsViewChange-choice-all-description": "Zeigt alle unvollständigen Karten mit einem *Fälligkeits*-Datum auf Boards, für die der Benutzer Berechtigungen hat.", + "broken-cards": "Fehlerhafte Karten", + "board-title-not-found": "Board „%s“ nicht gefunden.", + "swimlane-title-not-found": "Swimlane „%s“ nicht gefunden.", + "list-title-not-found": "Liste „%s“ nicht gefunden.", + "label-not-found": "Label „%s“ nicht gefunden.", + "label-color-not-found": "Label-Farbe „%s“ nicht gefunden.", + "user-username-not-found": "Nutzername „%s“ nicht gefunden.", + "comment-not-found": "Keine Karte gefunden, die „%s“ in einem Kommentar enthält.", + "globalSearch-title": "Alle Boards durchsuchen", + "no-cards-found": "Keine Karten gefunden", + "one-card-found": "Eine Karte gefunden", + "n-cards-found": "%s Karten gefunden", + "n-n-of-n-cards-found": "__start__–__end__ von __total__ Karten gefunden", + "operator-board": "Board", + "operator-board-abbrev": "b", + "operator-swimlane": "Swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "Liste", + "operator-list-abbrev": "l", + "operator-label": "Label", + "operator-label-abbrev": "#", + "operator-user": "Nutzer", + "operator-user-abbrev": "@", + "operator-member": "Mitglied", + "operator-member-abbrev": "m", + "operator-assignee": "Zugewiesener", + "operator-assignee-abbrev": "a", + "operator-creator": "Ersteller", + "operator-status": "Status", + "operator-due": "fällig", + "operator-created": "erstellt", + "operator-modified": "geändert", + "operator-sort": "sortieren", + "operator-comment": "Kommentar", + "operator-has": "hat", + "operator-limit": "Begrenzung", + "predicate-archived": "archiviert", + "predicate-open": "offen", + "predicate-ended": "beendet", + "predicate-all": "alle", + "predicate-overdue": "überfällig", + "predicate-week": "Woche", + "predicate-month": "Monat", + "predicate-quarter": "Quartal", + "predicate-year": "Jahr", + "predicate-due": "fällig", + "predicate-modified": "geändert", + "predicate-created": "erstellt", + "predicate-attachment": "Anhang", + "predicate-description": "Beschreibung", + "predicate-checklist": "Checkliste", + "predicate-start": "Start", + "predicate-end": "Ende", + "predicate-assignee": "Zugewiesener", + "predicate-member": "Mitglied", + "predicate-public": "öffentlich", + "predicate-private": "privat", + "operator-unknown-error": "„%s“ ist kein Operator", + "operator-number-expected": "Operator „__operator__“ erwartete eine Zahl, bekam aber „__value__“", + "operator-sort-invalid": "Sortierung „%s“ ist ungültig", + "operator-status-invalid": "„%s“ ist kein gültiger Status", + "operator-has-invalid": "%s ist keine gültige Prüfung auf Existenz", + "operator-limit-invalid": "%s ist keine gültige Begrenzung. Die Begrenzung sollte eine positive Ganzzahl sein.", + "next-page": "Nächste Seite", + "previous-page": "Vorherige Seite", + "heading-notes": "Bemerkungen", + "globalSearch-instructions-heading": "Hinweise zur Suche", + "globalSearch-instructions-description": "Suchanfragen können Operatoren enthalten, um die Suche zu verfeinern. Operatoren bestehen aus ihrem Namen und ihrem Wert, getrennt durch einen Doppelpunkt. Beispielsweise würde die Operatorangabe `Liste:Blockiert` die Suche beschränken auf Karten in einer Liste namens *Blockiert*. Wenn der Wert Leerschritte oder andere Spezialzeichen enthält, muss er in Anführungszeichen gesetzt sein (z.B. `__operator_list__:\"Im Review\"`).", + "globalSearch-instructions-operators": "Mögliche Operatoren:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` – Karten in Boards, auf die das angegebene *<title>* passt", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` – Karten in Listen, auf die das angegebene *<title>* passt", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` – Karten in Swimlanes, auf die das angebene *<title>* passt", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` – Karten mit einem Kommentar, das *<text>* enthält.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` – Karten, die ein Label haben, auf das *<color>* oder *<name> passt", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<Name|Farbe>` – Kurzform für `__operator_label__:<color>` oder `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` – Karten, für die *<username>* ein *Mitglied* oder ein *Zugewiesener* ist", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` – Kurzform für `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` – Karten, von denen *<username>* *Mitglied* ist", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` – Karten, denen *<username>* *zugewiesen* ist", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` – Karten, die von *<username>* angelegt wurden", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` – Karten, die spätestens in *<n>* Tagen fällig sind. `__operator_due__:__predicate_overdue__` zeigt alle Karten, für die die Fälligkeit überschritten ist.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` – Karten, die vor maximal *<n>* Tagen angelegt wurden", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` – Karten, die vor maximal *<n>* Tagen geändert wurden", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` – wo *<status>* eines der Folgenden ist:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` – archivierte Karten", + "globalSearch-instructions-status-all": "`__predicate_all__` – alle archivierten und unarchivierten Karten", + "globalSearch-instructions-status-ended": "`__predicate_ended__` – Karten mit einem Enddatum", + "globalSearch-instructions-status-public": "`__predicate_public__` – Karten aus öffentlich zugänglichen Boards", + "globalSearch-instructions-status-private": "`__predicate_private__` – Karten aus privaten Boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` – wo *<field>* eines aus `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` oder `__predicate_member__` ist. Die Angabe eines `-` vor *<field>* sucht nach leerem Feld (z.B. findet `__operator_has__:-fällig` alle Karten ohne Fälligkeitsdatum).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` – wo *<sort-name>* eines aus `__predicate_due__`, `__predicate_created__` oder `__predicate_modified__` ist. Zum absteigenden Sortieren ein `-` vor den Sortierschlüssel setzen.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` – wo *<n>* eine positive Ganzzahl ist, die die Anzahl Karten pro Seite darstellt.", + "globalSearch-instructions-notes-1": "Mehrere Operatoren können angegeben werden.", + "globalSearch-instructions-notes-2": "Gleichartige Operatoren werden Oder-verknüpft. Karten, für die eine Bedingung zutrifft, werden ausgegeben.\n`__operator_list__:Verfügbar __operator_list__:Blockiert` würde alle Karten ausgeben, die in irgendwelchen Listen mit den Namen *Verfügbar* oder *Blockiert* stehen.", + "globalSearch-instructions-notes-3": "Verschiedenartige Operatoren werden *UND*-verknüpft. Nur Karten, auf die alle verschiedenartigen Operatoren zutreffen, werden zurückgegeben. `__operator_list__:Verfügbar __operator_label__:Rot` gibt nur Karten aus der Liste *Verfügbar* mit *rotem* Label zurück.", + "globalSearch-instructions-notes-3-2": "Tage können als positive oder negative Ganzzahl angegeben werden, oder man nutzt `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` oder `__predicate_year__` für den jeweiligen Zeitraum.", + "globalSearch-instructions-notes-4": "Bei Suchen in Texten ist die Gross-/Kleinschreibung egal.", + "globalSearch-instructions-notes-5": "Per Vorgabe werden archivierte Karten bei der Suche nicht berücksichtigt.", + "link-to-search": "Link auf diese Suche", + "excel-font": "Arial", + "number": "Zahl", + "label-colors": "Label-Farben", + "label-names": "Label-Namen", + "archived-at": "archiviert am", + "sort-cards": "Sortiere Karten", + "cardsSortPopup-title": "Sortiere Karten", + "due-date": "Fälligkeitsdatum", + "server-error": "Server-Fehler", + "server-error-troubleshooting": "Bitte übermitteln Sie den Fehler, den der Server erzeugt hat.\nRufen Sie für eine Snap-Installation auf: `sudo snap logs wekan.wekan`\nRufen Sie für eine Docker-Installation auf: `sudo docker logs wekan-app`", + "title-alphabetically": "Überschrift (alphabetisch)", + "created-at-newest-first": "Erstelldatum (neueste zuerst)", + "created-at-oldest-first": "Erstelldatum (älteste zuerst)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Alle System-Nachrichten aller Nutzer verbergen", + "now-system-messages-of-all-users-are-hidden": "Alle System-Nachrichten aller Nutzer sind nun verborgen", + "move-swimlane": "Swimlane verschieben", + "moveSwimlanePopup-title": "Swimlane verschieben", + "custom-field-stringtemplate": "String-Vorlage", + "custom-field-stringtemplate-format": "Format (verwende %{value} als Platzhalter)", + "custom-field-stringtemplate-separator": "Trenner (verwende oder   für einen Leerschritt)", + "custom-field-stringtemplate-item-placeholder": "Drücke die Eingabetaste, um weitere Einträge hinzuzufügen", + "creator": "Ersteller", + "filesReportTitle": "Dateien-Bericht", + "orphanedFilesReportTitle": "Verwaister Datei-Bericht", + "reports": "Berichte", + "rulesReportTitle": "Regeln-Bericht", + "copy-swimlane": "Swimlane kopieren", + "copySwimlanePopup-title": "Swimlane kopieren", + "display-card-creator": "Zeige Karten-Erstellung", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Karte maximieren", + "minimize-card": "Karte minimieren", + "delete-org-warning-message": "Diese Organisation kann nicht gelöscht werden. Zumindest ein Benutzer ist ihr noch zugehörig.", + "delete-team-warning-message": "Dieses Team kann nicht gelöscht werden. Zumindest ein Benutzer ist ihm noch zugehörig." +} \ No newline at end of file diff --git a/i18n/de.i18n.json b/i18n/de.i18n.json index 57bcfd2d8..03b689b35 100644 --- a/i18n/de.i18n.json +++ b/i18n/de.i18n.json @@ -1,769 +1,1062 @@ { - "accept": "Akzeptieren", - "act-activity-notify": "Aktivitätsbenachrichtigung", - "act-addAttachment": "hat Anhang __attachment__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-deleteAttachment": "hat Anhang __attachment__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", - "act-addSubtask": "hat Teilaufgabe __subtask__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-addLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-addedLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-removeLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-removedLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-addChecklist": "hat Checkliste __checklist__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-addChecklistItem": "hat Checklistenposition __checklistItem__ zu Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-removeChecklist": "hat Checkliste __checklist__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-removeChecklistItem": "hat Checklistenposition __checklistItem__ von Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-checkedItem": "hat __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ abgehakt", - "act-uncheckedItem": "hat Haken von __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-completeChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", - "act-uncompleteChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ unvervollständigt", - "act-addComment": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ kommentiert: __comment__", - "act-editComment": "hat den Kommentar auf Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", - "act-deleteComment": "hat den Kommentar von Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", - "act-createBoard": "hat Board __board__ erstellt", - "act-createSwimlane": "hat Swimlane __swimlane__ in Board __board__ erstellt", - "act-createCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ erstellt", - "act-createCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ angelegt", - "act-deleteCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ gelöscht", - "act-setCustomField": "hat benutzerdefiniertes Feld __customField__: __customFieldValue__ auf Karte __card__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", - "act-createList": "hat Liste __list__ zu Board __board__ hinzugefügt", - "act-addBoardMember": "hat Mitglied __member__ zu Board __board__ hinzugefügt", - "act-archivedBoard": "hat Board __board__ ins Archiv verschoben", - "act-archivedCard": "hat Karte __card__ von der Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", - "act-archivedList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", - "act-archivedSwimlane": "hat Swimlane __swimlane__ von Board __board__ ins Archiv verschoben", - "act-importBoard": "hat Board __board__ importiert", - "act-importCard": "hat Karte __card__ in Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", - "act-importList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", - "act-joinMember": "hat Mitglied __member__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", - "act-moveCard": "hat Karte __card__ in Board __board__ von Liste __oldList__ in Swimlane __oldSwimlane__ zu Liste __list__ in Swimlane __swimlane__ verschoben", - "act-moveCardToOtherBoard": "hat Karte __card__ von Liste __oldList__ in Swimlane __oldSwimlane__ in Board __oldBoard__ zu Liste __list__ in Swimlane __swimlane__ in Board __board__ verschoben", - "act-removeBoardMember": "hat Mitglied __member__ von Board __board__ entfernt", - "act-restoredCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ wiederhergestellt", - "act-unjoinMember": "hat Mitglied __member__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Aktionen", - "activities": "Aktivitäten", - "activity": "Aktivität", - "activity-added": "hat %s zu %s hinzugefügt", - "activity-archived": "hat %s ins Archiv verschoben", - "activity-attached": "hat %s an %s angehängt", - "activity-created": "hat %s erstellt", - "activity-customfield-created": "hat das benutzerdefinierte Feld %s erstellt", - "activity-excluded": "hat %s von %s ausgeschlossen", - "activity-imported": "hat %s in %s von %s importiert", - "activity-imported-board": "hat %s von %s importiert", - "activity-joined": "ist %s beigetreten", - "activity-moved": "hat %s von %s nach %s verschoben", - "activity-on": "in %s", - "activity-removed": "hat %s von %s entfernt", - "activity-sent": "hat %s an %s gesendet", - "activity-unjoined": "hat %s verlassen", - "activity-subtask-added": "Teilaufgabe zu %s hinzugefügt", - "activity-checked-item": "markierte %s in Checkliste %s von %s", - "activity-unchecked-item": "hat %s in Checkliste %s von %s abgewählt", - "activity-checklist-added": "hat eine Checkliste zu %s hinzugefügt", - "activity-checklist-removed": "entfernte eine Checkliste von %s", - "activity-checklist-completed": "Abgeschlossene Checkliste", - "activity-checklist-uncompleted": "unvervollständigte die Checkliste %s von %s", - "activity-checklist-item-added": "hat ein Checklistenelement zu '%s' in %s hinzugefügt", - "activity-checklist-item-removed": "hat ein Checklistenelement von '%s' in %s entfernt", - "add": "Hinzufügen", - "activity-checked-item-card": "markiere %s in Checkliste %s", - "activity-unchecked-item-card": "hat %s in Checkliste %s abgewählt", - "activity-checklist-completed-card": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", - "activity-checklist-uncompleted-card": "unvervollständigte die Checkliste %s", - "activity-editComment": "editierte Kommentar", - "activity-deleteComment": "löschte Kommentar", - "add-attachment": "Datei anhängen", - "add-board": "neues Board", - "add-card": "Karte hinzufügen", - "add-swimlane": "Swimlane hinzufügen", - "add-subtask": "Teilaufgabe hinzufügen", - "add-checklist": "Checkliste hinzufügen", - "add-checklist-item": "Element zu Checkliste hinzufügen", - "add-cover": "Cover hinzufügen", - "add-label": "Label hinzufügen", - "add-list": "Liste hinzufügen", - "add-members": "Mitglieder hinzufügen", - "added": "Hinzugefügt", - "addMemberPopup-title": "Mitglieder", - "admin": "Admin", - "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", - "admin-announcement": "Ankündigung", - "admin-announcement-active": "Aktive systemweite Ankündigungen", - "admin-announcement-title": "Ankündigung des Administrators", - "all-boards": "Alle Boards", - "and-n-other-card": "und eine andere Karte", - "and-n-other-card_plural": "und __count__ andere Karten", - "apply": "Übernehmen", - "app-is-offline": "Laden, bitte warten. Das Aktualisieren der Seite führt zu Datenverlust. Wenn das Laden nicht funktioniert, überprüfen Sie bitte, ob der Server nicht angehalten wurde.", - "archive": "Ins Archiv verschieben", - "archive-all": "Alles ins Archiv verschieben", - "archive-board": "Board ins Archiv verschieben", - "archive-card": "Karte ins Archiv verschieben", - "archive-list": "Liste ins Archiv verschieben", - "archive-swimlane": "Swimlane ins Archiv verschieben", - "archive-selection": "Auswahl ins Archiv verschieben", - "archiveBoardPopup-title": "Board ins Archiv verschieben?", - "archived-items": "Archiv", - "archived-boards": "Boards im Archiv", - "restore-board": "Board wiederherstellen", - "no-archived-boards": "Keine Boards im Archiv.", - "archives": "Archiv", - "template": "Vorlage", - "templates": "Vorlagen", - "assign-member": "Mitglied zuweisen", - "attached": "angehängt", - "attachment": "Anhang", - "attachment-delete-pop": "Das Löschen eines Anhangs kann nicht rückgängig gemacht werden.", - "attachmentDeletePopup-title": "Anhang löschen?", - "attachments": "Anhänge", - "auto-watch": "Neue Boards nach Erstellung automatisch beobachten", - "avatar-too-big": "Das Profilbild ist zu groß (max. 70KB)", - "back": "Zurück", - "board-change-color": "Farbe ändern", - "board-nb-stars": "%s Sterne", - "board-not-found": "Board nicht gefunden", - "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", - "board-public-info": "Dieses Board wird <strong>öffentlich</strong> sein.", - "boardChangeColorPopup-title": "Farbe des Boards ändern", - "boardChangeTitlePopup-title": "Board umbenennen", - "boardChangeVisibilityPopup-title": "Sichtbarkeit ändern", - "boardChangeWatchPopup-title": "Beobachtung ändern", - "boardMenuPopup-title": "Boardeinstellungen", - "boardChangeViewPopup-title": "Boardansicht", - "boards": "Boards", - "board-view": "Boardansicht", - "board-view-cal": "Kalender", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Einklappen", - "board-view-lists": "Listen", - "bucket-example": "z.B. \"Löffelliste\"", - "cancel": "Abbrechen", - "card-archived": "Diese Karte wurde ins Archiv verschoben", - "board-archived": "Dieses Board wurde ins Archiv verschoben.", - "card-comments-title": "Diese Karte hat %s Kommentar(e).", - "card-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Alle Aktionen, die dieser Karte zugeordnet sind, werden ebenfalls gelöscht.", - "card-delete-pop": "Alle Aktionen werden aus dem Aktivitätsfeed entfernt und die Karte kann nicht wiedereröffnet werden. Die Aktion kann nicht rückgängig gemacht werden.", - "card-delete-suggest-archive": "Sie können eine Karte ins Archiv verschieben, um sie vom Board zu entfernen und die Aktivitäten zu behalten.", - "card-due": "Fällig", - "card-due-on": "Fällig am", - "card-spent": "Aufgewendete Zeit", - "card-edit-attachments": "Anhänge ändern", - "card-edit-custom-fields": "Benutzerdefinierte Felder editieren", - "card-edit-labels": "Labels ändern", - "card-edit-members": "Mitglieder ändern", - "card-labels-title": "Labels für diese Karte ändern.", - "card-members-title": "Der Karte Board-Mitglieder hinzufügen oder entfernen.", - "card-start": "Start", - "card-start-on": "Start am", - "cardAttachmentsPopup-title": "Anhängen von", - "cardCustomField-datePopup-title": "Datum ändern", - "cardCustomFieldsPopup-title": "Benutzerdefinierte Felder editieren", - "cardDeletePopup-title": "Karte löschen?", - "cardDetailsActionsPopup-title": "Kartenaktionen", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Mitglieder", - "cardMorePopup-title": "Mehr", - "cardTemplatePopup-title": "Vorlage erstellen", - "cards": "Karten", - "cards-count": "Karten", - "casSignIn": "Mit CAS anmelden", - "cardType-card": "Karte", - "cardType-linkedCard": "Verknüpfte Karte", - "cardType-linkedBoard": "Verknüpftes Board", - "change": "Ändern", - "change-avatar": "Profilbild ändern", - "change-password": "Passwort ändern", - "change-permissions": "Berechtigungen ändern", - "change-settings": "Einstellungen ändern", - "changeAvatarPopup-title": "Profilbild ändern", - "changeLanguagePopup-title": "Sprache ändern", - "changePasswordPopup-title": "Passwort ändern", - "changePermissionsPopup-title": "Berechtigungen ändern", - "changeSettingsPopup-title": "Einstellungen ändern", - "subtasks": "Teilaufgaben", - "checklists": "Checklisten", - "click-to-star": "Klicken Sie, um das Board mit einem Stern zu markieren.", - "click-to-unstar": "Klicken Sie, um den Stern vom Board zu entfernen.", - "clipboard": "Zwischenablage oder Drag & Drop", - "close": "Schließen", - "close-board": "Board schließen", - "close-board-pop": "Sie können das Board wiederherstellen, indem Sie die Schaltfläche \"Archiv\" in der Kopfzeile der Startseite anklicken.", - "color-black": "schwarz", - "color-blue": "blau", - "color-crimson": "Karminrot", - "color-darkgreen": "Dunkelgrün", - "color-gold": "Gold", - "color-gray": "Grau", - "color-green": "grün", - "color-indigo": "Indigo", - "color-lime": "hellgrün", - "color-magenta": "Magentarot", - "color-mistyrose": "Altrosa", - "color-navy": "Marineblau", - "color-orange": "orange", - "color-paleturquoise": "Blasses Türkis", - "color-peachpuff": "Pfirsich", - "color-pink": "pink", - "color-plum": "Pflaume", - "color-purple": "lila", - "color-red": "rot", - "color-saddlebrown": "Sattelbraun", - "color-silver": "Silber", - "color-sky": "himmelblau", - "color-slateblue": "Schieferblau", - "color-white": "Weiß", - "color-yellow": "gelb", - "unset-color": "Nicht festgelegt", - "comment": "Kommentar", - "comment-placeholder": "Kommentar schreiben", - "comment-only": "Nur Kommentare", - "comment-only-desc": "Kann Karten nur kommentieren.", - "no-comments": "Keine Kommentare", - "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", - "worker": "Arbeiter", - "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?", - "confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?", - "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", - "linkCardPopup-title": "Karte verknüpfen", - "searchElementPopup-title": "Suche", - "copyCardPopup-title": "Karte kopieren", - "copyChecklistToManyCardsPopup-title": "Checklistenvorlage in mehrere Karten kopieren", - "copyChecklistToManyCardsPopup-instructions": "Titel und Beschreibungen der Zielkarten im folgenden JSON-Format", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", - "create": "Erstellen", - "createBoardPopup-title": "Board erstellen", - "chooseBoardSourcePopup-title": "Board importieren", - "createLabelPopup-title": "Label erstellen", - "createCustomField": "Feld erstellen", - "createCustomFieldPopup-title": "Feld erstellen", - "current": "aktuell", - "custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.", - "custom-field-checkbox": "Kontrollkästchen", - "custom-field-date": "Datum", - "custom-field-dropdown": "Dropdownliste", - "custom-field-dropdown-none": "(keiner)", - "custom-field-dropdown-options": "Listenoptionen", - "custom-field-dropdown-options-placeholder": "Drücken Sie die Eingabetaste, um weitere Optionen hinzuzufügen", - "custom-field-dropdown-unknown": "(unbekannt)", - "custom-field-number": "Zahl", - "custom-field-text": "Text", - "custom-fields": "Benutzerdefinierte Felder", - "date": "Datum", - "decline": "Ablehnen", - "default-avatar": "Standard Profilbild", - "delete": "Löschen", - "deleteCustomFieldPopup-title": "Benutzerdefiniertes Feld löschen?", - "deleteLabelPopup-title": "Label löschen?", - "description": "Beschreibung", - "disambiguateMultiLabelPopup-title": "Labels vereinheitlichen", - "disambiguateMultiMemberPopup-title": "Mitglieder vereinheitlichen", - "discard": "Verwerfen", - "done": "Erledigt", - "download": "Herunterladen", - "edit": "Bearbeiten", - "edit-avatar": "Profilbild ändern", - "edit-profile": "Profil ändern", - "edit-wip-limit": "WIP-Limit bearbeiten", - "soft-wip-limit": "Soft WIP-Limit", - "editCardStartDatePopup-title": "Startdatum ändern", - "editCardDueDatePopup-title": "Fälligkeitsdatum ändern", - "editCustomFieldPopup-title": "Feld bearbeiten", - "editCardSpentTimePopup-title": "Aufgewendete Zeit ändern", - "editLabelPopup-title": "Label ändern", - "editNotificationPopup-title": "Benachrichtigung ändern", - "editProfilePopup-title": "Profil ändern", - "email": "E-Mail", - "email-enrollAccount-subject": "Ihr Benutzerkonto auf __siteName__ wurde erstellt", - "email-enrollAccount-text": "Hallo __user__,\n\num den Dienst nutzen zu können, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", - "email-fail": "Senden der E-Mail fehlgeschlagen", - "email-fail-text": "Fehler beim Senden der E-Mail", - "email-invalid": "Ungültige E-Mail-Adresse", - "email-invite": "per E-Mail einladen", - "email-invite-subject": "__inviter__ hat Ihnen eine Einladung geschickt", - "email-invite-text": "Hallo __user__,\n\n__inviter__ hat Sie zu dem Board \"__board__\" eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n\n__url__\n\nDanke.", - "email-resetPassword-subject": "Setzten Sie ihr Passwort auf __siteName__ zurück", - "email-resetPassword-text": "Hallo __user__,\n\num ihr Passwort zurückzusetzen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", - "email-sent": "E-Mail gesendet", - "email-verifyEmail-subject": "Bestätigen Sie ihre E-Mail-Adresse auf __siteName__", - "email-verifyEmail-text": "Hallo __user__,\n\num ihre E-Mail-Adresse zu bestätigen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", - "enable-wip-limit": "WIP-Limit einschalten", - "error-board-doesNotExist": "Dieses Board existiert nicht", - "error-board-notAdmin": "Um das zu tun, müssen Sie Administrator dieses Boards sein", - "error-board-notAMember": "Um das zu tun, müssen Sie Mitglied dieses Boards sein", - "error-json-malformed": "Ihre Eingabe ist kein gültiges JSON", - "error-json-schema": "Ihre JSON-Daten enthalten nicht die gewünschten Informationen im richtigen Format", - "error-list-doesNotExist": "Diese Liste existiert nicht", - "error-user-doesNotExist": "Dieser Nutzer existiert nicht", - "error-user-notAllowSelf": "Sie können sich nicht selbst einladen.", - "error-user-notCreated": "Dieser Nutzer ist nicht angelegt", - "error-username-taken": "Dieser Benutzername ist bereits vergeben", - "error-email-taken": "E-Mail wird schon verwendet", - "export-board": "Board exportieren", - "sort": "Sortieren", - "sort-desc": "Zum Sortieren der Liste klicken", - "list-sort-by": "Sortieren der Liste nach:", - "list-label-modifiedAt": "Letzte Zugriffszeit", - "list-label-title": "Name der Liste", - "list-label-sort": "Ihre manuelle Sortierung", - "list-label-short-modifiedAt": "(Z)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Karten oder Listen filtern", - "list-filter-label": "Liste nach Titel filtern", - "filter-clear": "Filter entfernen", - "filter-no-label": "Kein Label", - "filter-no-member": "Kein Mitglied", - "filter-no-custom-fields": "Keine benutzerdefinierten Felder", - "filter-show-archive": "Archivierte Listen anzeigen", - "filter-hide-empty": "Leere Listen verstecken", - "filter-on": "Filter ist aktiv", - "filter-on-desc": "Sie filtern die Karten in diesem Board. Klicken Sie, um den Filter zu bearbeiten.", - "filter-to-selection": "Ergebnisse auswählen", - "advanced-filter-label": "Erweiterter Filter", - "advanced-filter-description": "Der erweiterte Filter erlaubt die Eingabe von Zeichenfolgen, die folgende Operatoren enthalten: == != <= >= && || ( ). Ein Leerzeichen wird als Trennzeichen zwischen den Operatoren verwendet. Sie können nach allen benutzerdefinierten Feldern filtern, indem Sie deren Namen und Werte eingeben. Zum Beispiel: Feld1 == Wert1. Hinweis: Wenn Felder oder Werte Leerzeichen enthalten, müssen Sie sie in einfache Anführungszeichen setzen. Zum Beispiel: 'Feld 1' == 'Wert 1'. Um einzelne Steuerzeichen (' \\/) zu überspringen, können Sie \\ verwenden. Zum Beispiel: Feld1 == Ich bin\\'s. Sie können außerdem mehrere Bedingungen kombinieren. Zum Beispiel: F1 == W1 || F1 == W2. Normalerweise werden alle Operatoren von links nach rechts interpretiert. Sie können die Reihenfolge ändern, indem Sie Klammern setzen. Zum Beispiel: F1 == W1 && ( F2 == W2 || F2 == W3 ). Sie können Textfelder auch mithilfe regulärer Ausdrücke durchsuchen: F1 == /Tes.*/i", - "fullname": "Vollständiger Name", - "header-logo-title": "Zurück zur Board Seite.", - "hide-system-messages": "Systemmeldungen ausblenden", - "headerBarCreateBoardPopup-title": "Board erstellen", - "home": "Home", - "import": "Importieren", - "link": "Verknüpfung", - "import-board": "Board importieren", - "import-board-c": "Board importieren", - "import-board-title-trello": "Board von Trello importieren", - "import-board-title-wekan": "Board aus vorherigem Export importieren", - "import-sandstorm-backup-warning": "Löschen Sie keine Daten, die Sie aus einem ursprünglich exportierten oder Trelloboard importieren, bevor Sie geprüft haben, ob alles funktioniert. Andernfalls kann es zu Datenverlust kommen, falls es zu einem \"Board nicht gefunden\"-Fehler kommt.", - "import-sandstorm-warning": "Das importierte Board wird alle bereits existierenden Daten löschen und mit den importierten Daten überschreiben.", - "from-trello": "Von Trello", - "from-wekan": "Aus vorherigem Export", - "import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text", - "import-board-instruction-wekan": "Gehen Sie in Ihrem Board auf 'Menü', danach auf 'Board exportieren' und kopieren Sie den Text aus der heruntergeladenen Datei.", - "import-board-instruction-about-errors": "Treten beim importieren eines Board Fehler auf, so kann der Import dennoch erfolgreich abgeschlossen sein und das Board ist auf der Seite \"Alle Boards\" zusehen.", - "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein", - "import-map-members": "Mitglieder zuordnen", - "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen sie die Mitglieder, die Sie importieren wollen, Ihren Benutzern zu.", - "import-show-user-mapping": "Mitgliederzuordnung überprüfen", - "import-user-select": "Wählen Sie den bestehenden Benutzer aus, den Sie für dieses Mitglied verwenden wollen.", - "importMapMembersAddPopup-title": "Mitglied auswählen", - "info": "Version", - "initials": "Initialen", - "invalid-date": "Ungültiges Datum", - "invalid-time": "Ungültige Zeitangabe", - "invalid-user": "Ungültiger Benutzer", - "joined": "beigetreten", - "just-invited": "Sie wurden soeben zu diesem Board eingeladen", - "keyboard-shortcuts": "Tastaturkürzel", - "label-create": "Label erstellen", - "label-default": "%s Label (Standard)", - "label-delete-pop": "Aktion kann nicht rückgängig gemacht werden. Das Label wird von allen Karten entfernt und seine Historie gelöscht.", - "labels": "Labels", - "language": "Sprache", - "last-admin-desc": "Sie können keine Rollen ändern, weil es mindestens einen Administrator geben muss.", - "leave-board": "Board verlassen", - "leave-board-pop": "Sind Sie sicher, dass Sie __boardTitle__ verlassen möchten? Sie werden von allen Karten in diesem Board entfernt.", - "leaveBoardPopup-title": "Board verlassen?", - "link-card": "Link zu dieser Karte", - "list-archive-cards": "Alle Karten dieser Liste ins Archiv verschieben", - "list-archive-cards-pop": "Alle Karten dieser Liste werden vom Board entfernt. Um Karten im Papierkorb anzuzeigen und wiederherzustellen, klicken Sie auf \"Menü\" > \"Archiv\".", - "list-move-cards": "Alle Karten in dieser Liste verschieben", - "list-select-cards": "Alle Karten in dieser Liste auswählen", - "set-color-list": "Lege Farbe fest", - "listActionPopup-title": "Listenaktionen", - "swimlaneActionPopup-title": "Swimlaneaktionen", - "swimlaneAddPopup-title": "Swimlane unterhalb einfügen", - "listImportCardPopup-title": "Eine Trello-Karte importieren", - "listMorePopup-title": "Mehr", - "link-list": "Link zu dieser Liste", - "list-delete-pop": "Alle Aktionen werden aus dem Verlauf gelöscht und die Liste kann nicht wiederhergestellt werden.", - "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", - "lists": "Listen", - "swimlanes": "Swimlanes", - "log-out": "Ausloggen", - "log-in": "Einloggen", - "loginPopup-title": "Einloggen", - "memberMenuPopup-title": "Nutzereinstellungen", - "members": "Mitglieder", - "menu": "Menü", - "move-selection": "Auswahl verschieben", - "moveCardPopup-title": "Karte verschieben", - "moveCardToBottom-title": "Ans Ende verschieben", - "moveCardToTop-title": "Zum Anfang verschieben", - "moveSelectionPopup-title": "Auswahl verschieben", - "multi-selection": "Mehrfachauswahl", - "multi-selection-on": "Mehrfachauswahl ist aktiv", - "muted": "Stumm", - "muted-info": "Sie werden nicht über Änderungen auf diesem Board benachrichtigt", - "my-boards": "Meine Boards", - "name": "Name", - "no-archived-cards": "Keine Karten im Archiv.", - "no-archived-lists": "Keine Listen im Archiv.", - "no-archived-swimlanes": "Keine Swimlanes im Archiv.", - "no-results": "Keine Ergebnisse", - "normal": "Normal", - "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", - "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", - "notify-participate": "Benachrichtigungen zu allen Karten erhalten, an denen Sie teilnehmen", - "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", - "optional": "optional", - "or": "oder", - "page-maybe-private": "Diese Seite könnte privat sein. Vielleicht können Sie sie sehen, wenn Sie sich <a href='%s'>einloggen</a>.", - "page-not-found": "Seite nicht gefunden.", - "password": "Passwort", - "paste-or-dragdrop": "Einfügen oder Datei mit Drag & Drop ablegen (nur Bilder)", - "participating": "Teilnehmen", - "preview": "Vorschau", - "previewAttachedImagePopup-title": "Vorschau", - "previewClipboardImagePopup-title": "Vorschau", - "private": "Privat", - "private-desc": "Dieses Board ist privat. Nur Nutzer, die zu dem Board gehören, können es anschauen und bearbeiten.", - "profile": "Profil", - "public": "Öffentlich", - "public-desc": "Dieses Board ist öffentlich. Es ist für jeden, der den Link kennt, sichtbar und taucht in Suchmaschinen wie Google auf. Nur Nutzer, die zum Board hinzugefügt wurden, können es bearbeiten.", - "quick-access-description": "Markieren Sie ein Board mit einem Stern, um dieser Leiste eine Verknüpfung hinzuzufügen.", - "remove-cover": "Cover entfernen", - "remove-from-board": "Von Board entfernen", - "remove-label": "Label entfernen", - "listDeletePopup-title": "Liste löschen?", - "remove-member": "Nutzer entfernen", - "remove-member-from-card": "Von Karte entfernen", - "remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.", - "removeMemberPopup-title": "Mitglied entfernen?", - "rename": "Umbenennen", - "rename-board": "Board umbenennen", - "restore": "Wiederherstellen", - "save": "Speichern", - "search": "Suchen", - "rules": "Regeln", - "search-cards": "Suche nach Karten-/Listentiteln, Beschreibungen und personalisierten Feldern auf diesem Brett ", - "search-example": "Suchbegriff", - "select-color": "Farbe auswählen", - "set-wip-limit-value": "Setzen Sie ein Limit für die maximale Anzahl von Aufgaben in dieser Liste", - "setWipLimitPopup-title": "WIP-Limit setzen", - "shortcut-assign-self": "Fügen Sie sich zur aktuellen Karte hinzu", - "shortcut-autocomplete-emoji": "Emojis vervollständigen", - "shortcut-autocomplete-members": "Mitglieder vervollständigen", - "shortcut-clear-filters": "Alle Filter entfernen", - "shortcut-close-dialog": "Dialog schließen", - "shortcut-filter-my-cards": "Meine Karten filtern", - "shortcut-show-shortcuts": "Liste der Tastaturkürzel anzeigen", - "shortcut-toggle-filterbar": "Filter-Seitenleiste ein-/ausblenden", - "shortcut-toggle-sidebar": "Seitenleiste ein-/ausblenden", - "show-cards-minimum-count": "Zeigt die Kartenanzahl an, wenn die Liste mehr enthält als", - "sidebar-open": "Seitenleiste öffnen", - "sidebar-close": "Seitenleiste schließen", - "signupPopup-title": "Benutzerkonto erstellen", - "star-board-title": "Klicken Sie, um das Board mit einem Stern zu markieren. Es erscheint dann oben in ihrer Boardliste.", - "starred-boards": "Markierte Boards", - "starred-boards-description": "Markierte Boards erscheinen oben in ihrer Boardliste.", - "subscribe": "Abonnieren", - "team": "Team", - "this-board": "diesem Board", - "this-card": "diese Karte", - "spent-time-hours": "Aufgewendete Zeit (Stunden)", - "overtime-hours": "Mehrarbeit (Stunden)", - "overtime": "Mehrarbeit", - "has-overtime-cards": "Hat Karten mit Mehrarbeit", - "has-spenttime-cards": "Hat Karten mit aufgewendeten Zeiten", - "time": "Zeit", - "title": "Titel", - "tracking": "Folgen", - "tracking-info": "Sie werden über alle Änderungen an Karten benachrichtigt, an denen Sie als Ersteller oder Mitglied beteiligt sind.", - "type": "Typ", - "unassign-member": "Mitglied entfernen", - "unsaved-description": "Sie haben eine nicht gespeicherte Änderung.", - "unwatch": "Beobachtung entfernen", - "upload": "Upload", - "upload-avatar": "Profilbild hochladen", - "uploaded-avatar": "Profilbild hochgeladen", - "username": "Benutzername", - "view-it": "Ansehen", - "warn-list-archived": "Warnung: Diese Karte befindet sich in einer Liste im Archiv", - "watch": "Beobachten", - "watching": "Beobachten", - "watching-info": "Sie werden über alle Änderungen in diesem Board benachrichtigt", - "welcome-board": "Willkommen-Board", - "welcome-swimlane": "Meilenstein 1", - "welcome-list1": "Grundlagen", - "welcome-list2": "Fortgeschritten", - "card-templates-swimlane": "Kartenvorlagen", - "list-templates-swimlane": "Listenvorlagen", - "board-templates-swimlane": "Boardvorlagen", - "what-to-do": "Was wollen Sie tun?", - "wipLimitErrorPopup-title": "Ungültiges WIP-Limit", - "wipLimitErrorPopup-dialog-pt1": "Die Anzahl von Aufgaben in dieser Liste ist größer als das von Ihnen definierte WIP-Limit.", - "wipLimitErrorPopup-dialog-pt2": "Bitte verschieben Sie einige Aufgaben aus dieser Liste oder setzen Sie ein grösseres WIP-Limit.", - "admin-panel": "Administration", - "settings": "Einstellungen", - "people": "Nutzer", - "registration": "Registrierung", - "disable-self-registration": "Selbstregistrierung deaktivieren", - "invite": "Einladen", - "invite-people": "Nutzer einladen", - "to-boards": "In Board(s)", - "email-addresses": "E-Mail Adressen", - "smtp-host-description": "Die Adresse Ihres SMTP-Servers für ausgehende E-Mails.", - "smtp-port-description": "Der Port Ihres SMTP-Servers für ausgehende E-Mails.", - "smtp-tls-description": "Aktiviere TLS Unterstützung für SMTP Server", - "smtp-host": "SMTP-Server", - "smtp-port": "SMTP-Port", - "smtp-username": "Benutzername", - "smtp-password": "Passwort", - "smtp-tls": "TLS Unterstützung", - "send-from": "Absender", - "send-smtp-test": "Test-E-Mail an sich selbst schicken", - "invitation-code": "Einladungscode", - "email-invite-register-subject": "__inviter__ hat Ihnen eine Einladung geschickt", - "email-invite-register-text": "Sehr geehrte(r) __user__,\n\n__inviter__ hat Sie zur Mitarbeit an einem Kanbanboard eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n__url__\n\nIhr Einladungscode lautet: __icode__\n\nDanke.", - "email-smtp-test-subject": "SMTP Test-E-Mail", - "email-smtp-test-text": "Sie haben erfolgreich eine E-Mail versandt", - "error-invitation-code-not-exist": "Ungültiger Einladungscode", - "error-notAuthorized": "Sie sind nicht berechtigt diese Seite zu sehen.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional für Authentifizierung)", - "outgoing-webhooks": "Ausgehende Webhooks", - "bidirectional-webhooks": "Zwei-Wege Webhooks", - "outgoingWebhooksPopup-title": "Ausgehende Webhooks", - "boardCardTitlePopup-title": "Kartentitelfilter", - "disable-webhook": "Diesen Webhook deaktivieren", - "global-webhook": "Globale Webhooks", - "new-outgoing-webhook": "Neuer ausgehender Webhook", - "no-name": "(Unbekannt)", - "Node_version": "Node-Version", - "Meteor_version": "Meteor-Version", - "MongoDB_version": "MongoDB-Version", - "MongoDB_storage_engine": "MongoDB-Speicher-Engine", - "MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert", - "OS_Arch": "Betriebssystem-Architektur", - "OS_Cpus": "Anzahl Prozessoren", - "OS_Freemem": "Freier Arbeitsspeicher", - "OS_Loadavg": "Mittlere Systembelastung", - "OS_Platform": "Plattform", - "OS_Release": "Version des Betriebssystem", - "OS_Totalmem": "Gesamter Arbeitsspeicher", - "OS_Type": "Typ des Betriebssystems", - "OS_Uptime": "Laufzeit des Systems", - "days": "Tage", - "hours": "Stunden", - "minutes": "Minuten", - "seconds": "Sekunden", - "show-field-on-card": "Zeige dieses Feld auf der Karte", - "automatically-field-on-card": "Automatisch Label für alle Karten erzeugen", - "showLabel-field-on-card": "Feldbezeichnung auf Minikarte anzeigen", - "yes": "Ja", - "no": "Nein", - "accounts": "Konten", - "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", - "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", - "createdAt": "Erstellt am", - "verified": "Geprüft", - "active": "Aktiv", - "card-received": "Empfangen", - "card-received-on": "Empfangen am", - "card-end": "Ende", - "card-end-on": "Endet am", - "editCardReceivedDatePopup-title": "Empfangsdatum ändern", - "editCardEndDatePopup-title": "Enddatum ändern", - "setCardColorPopup-title": "Farbe festlegen", - "setCardActionsColorPopup-title": "Farbe wählen", - "setSwimlaneColorPopup-title": "Farbe wählen", - "setListColorPopup-title": "Farbe wählen", - "assigned-by": "Zugewiesen von", - "requested-by": "Angefordert von", - "board-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Sie werden alle Listen, Karten und Aktionen, die mit diesem Board verbunden sind, verlieren.", - "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", - "boardDeletePopup-title": "Board löschen?", - "delete-board": "Board löschen", - "default-subtasks-board": "Teilaufgabe für __board__ Board", - "default": "Standard", - "queue": "Warteschlange", - "subtask-settings": "Einstellungen für Teilaufgaben", - "card-settings": "Karten-Einstellungen", - "boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben", - "boardCardSettingsPopup-title": "Karten-Einstellungen", - "deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:", - "deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:", - "show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:", - "prefix-with-full-path": "Vollständiger Pfad über Titel", - "prefix-with-parent": "Über Titel", - "subtext-with-full-path": "Vollständiger Pfad unter Titel", - "subtext-with-parent": "Unter Titel", - "change-card-parent": "Übergeordnete Karte ändern", - "parent-card": "Übergeordnete Karte", - "source-board": "Quellboard", - "no-parent": "Nicht anzeigen", - "activity-added-label": "fügte Label '%s' zu %s hinzu", - "activity-removed-label": "entfernte Label '%s' von %s", - "activity-delete-attach": "löschte ein Anhang von %s", - "activity-added-label-card": "Label hinzugefügt '%s'", - "activity-removed-label-card": "Label entfernt '%s'", - "activity-delete-attach-card": "hat einen Anhang gelöscht", - "activity-set-customfield": "setze benutzerdefiniertes Feld '%s' zu '%s' in %s", - "activity-unset-customfield": "entferne benutzerdefiniertes Feld '%s' in %s", - "r-rule": "Regel", - "r-add-trigger": "Auslöser hinzufügen", - "r-add-action": "Aktion hinzufügen", - "r-board-rules": "Boardregeln", - "r-add-rule": "Regel hinzufügen", - "r-view-rule": "Regel anzeigen", - "r-delete-rule": "Regel löschen", - "r-new-rule-name": "Neuer Regeltitel", - "r-no-rules": "Keine Regeln", - "r-when-a-card": "Wenn Karte", - "r-is": "wird", - "r-is-moved": "verschoben wird", - "r-added-to": "hinzugefügt zu", - "r-removed-from": "entfernt von", - "r-the-board": "das Board", - "r-list": "Liste", - "set-filter": "Setze Filter", - "r-moved-to": "verschoben nach", - "r-moved-from": "verschoben von", - "r-archived": "ins Archiv verschoben", - "r-unarchived": "aus dem Archiv wiederhergestellt", - "r-a-card": "einer Karte", - "r-when-a-label-is": "Wenn ein Label", - "r-when-the-label": "Wenn das Label", - "r-list-name": "Listenname", - "r-when-a-member": "Wenn ein Mitglied", - "r-when-the-member": "Wenn das Mitglied", - "r-name": "Name", - "r-when-a-attach": "Wenn ein Anhang", - "r-when-a-checklist": "Wenn eine Checkliste wird", - "r-when-the-checklist": "Wenn die Checkliste", - "r-completed": "abgeschlossen", - "r-made-incomplete": "unvollständig gemacht", - "r-when-a-item": "Wenn eine Checklistenposition", - "r-when-the-item": "Wenn die Checklistenposition", - "r-checked": "markiert wird", - "r-unchecked": "abgewählt wird", - "r-move-card-to": "Verschiebe Karte an", - "r-top-of": "Anfang von", - "r-bottom-of": "Ende von", - "r-its-list": "seiner Liste", - "r-archive": "Ins Archiv verschieben", - "r-unarchive": "Aus dem Archiv wiederherstellen", - "r-card": "Karte", - "r-add": "Hinzufügen", - "r-remove": "entfernen", - "r-label": "Label", - "r-member": "Mitglied", - "r-remove-all": "Entferne alle Mitglieder von der Karte", - "r-set-color": "Farbe festlegen auf", - "r-checklist": "Checkliste", - "r-check-all": "Alle markieren", - "r-uncheck-all": "Alle abwählen", - "r-items-check": "Elemente der Checkliste", - "r-check": "Markieren", - "r-uncheck": "Abwählen", - "r-item": "Element", - "r-of-checklist": "der Checkliste", - "r-send-email": "Eine E-Mail senden", - "r-to": "an", - "r-subject": "Betreff", - "r-rule-details": "Regeldetails", - "r-d-move-to-top-gen": "Karte nach oben in die Liste verschieben", - "r-d-move-to-top-spec": "Karte an den Anfang der Liste verschieben", - "r-d-move-to-bottom-gen": "Karte nach unten in die Liste verschieben", - "r-d-move-to-bottom-spec": "Karte an das Ende der Liste verschieben", - "r-d-send-email": "E-Mail senden", - "r-d-send-email-to": "an", - "r-d-send-email-subject": "Betreff", - "r-d-send-email-message": "Nachricht", - "r-d-archive": "Karte ins Archiv verschieben", - "r-d-unarchive": "Karte aus dem Archiv wiederherstellen", - "r-d-add-label": "Label hinzufügen", - "r-d-remove-label": "Label entfernen", - "r-create-card": "Neue Karte erstellen", - "r-in-list": "in der Liste", - "r-in-swimlane": "in Swimlane", - "r-d-add-member": "Mitglied hinzufügen", - "r-d-remove-member": "Mitglied entfernen", - "r-d-remove-all-member": "Entferne alle Mitglieder", - "r-d-check-all": "Alle Elemente der Liste markieren", - "r-d-uncheck-all": "Alle Element der Liste abwählen", - "r-d-check-one": "Element auswählen", - "r-d-uncheck-one": "Element abwählen", - "r-d-check-of-list": "der Checkliste", - "r-d-add-checklist": "Checkliste hinzufügen", - "r-d-remove-checklist": "Checkliste entfernen", - "r-by": "durch", - "r-add-checklist": "Checkliste hinzufügen", - "r-with-items": "mit Elementen", - "r-items-list": "Element1,Element2,Element3", - "r-add-swimlane": "Füge Swimlane hinzu", - "r-swimlane-name": "Swimlanename", - "r-board-note": "Hinweis: Lassen Sie ein Feld leer, um alle möglichen Werte zu finden.", - "r-checklist-note": "Hinweis: Die Elemente der Checkliste müssen als kommagetrennte Werte geschrieben werden.", - "r-when-a-card-is-moved": "Wenn eine Karte in eine andere Liste verschoben wird", - "r-set": "Setze", - "r-update": "Aktualisiere", - "r-datefield": "Datumsfeld", - "r-df-start-at": "Start", - "r-df-due-at": "Fällig", - "r-df-end-at": "Ende", - "r-df-received-at": "Empfangen", - "r-to-current-datetime": "auf das aktuelle Datum/Zeit", - "r-remove-value-from": "Entferne Wert von", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authentifizierungsmethode", - "authentication-type": "Authentifizierungstyp", - "custom-product-name": "Benutzerdefinierter Produktname", - "layout": "Layout", - "hide-logo": "Verstecke Logo", - "add-custom-html-after-body-start": "Füge benutzerdefiniertes HTML nach <body> Anfang hinzu", - "add-custom-html-before-body-end": "Füge benutzerdefiniertes HTML vor </body>Ende hinzu", - "error-undefined": "Etwas ist schief gelaufen", - "error-ldap-login": "Es ist ein Fehler beim Anmelden aufgetreten", - "display-authentication-method": "Anzeige Authentifizierungsverfahren", - "default-authentication-method": "Standardauthentifizierungsverfahren", - "duplicate-board": "Board duplizieren", - "people-number": "Anzahl der Personen:", - "swimlaneDeletePopup-title": "Swimlane löschen?", - "swimlane-delete-pop": "Alle Aktionen werden aus dem Aktivitätenfeed entfernt und die Swimlane kann nicht wiederhergestellt werden. Die Aktion kann nicht rückgängig gemacht werden.", - "restore-all": "Alles wiederherstellen", - "delete-all": "Alles löschen", - "loading": "Laden, bitte warten.", - "previous_as": "letzter Zeitpunkt war", - "act-a-dueAt": "hat Fälligkeit geändert auf\nWann: __timeValue__\nWo: __card__\nvorheriger Fälligkeitszeitpunkt war __timeOldValue__", - "act-a-endAt": "hat Ende auf __timeValue__ von (__timeOldValue__) geändert", - "act-a-startAt": "hat Start auf __timeValue__ von (__timeOldValue__) geändert", - "act-a-receivedAt": "hat Empfangszeit auf __timeValue__ von (__timeOldValue__) geändert", - "a-dueAt": "hat Fälligkeit geändert auf", - "a-endAt": "hat Ende geändert auf", - "a-startAt": "hat Startzeit geändert auf", - "a-receivedAt": "hat Empfangszeit geändert auf", - "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", - "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", - "duenow": "aktuelles Fälligkeitsdatum %s heute", - "act-newDue": "__list__/__card__ hat seine 1. fällige Erinnerung [__board__]", - "act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]", - "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", - "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", - "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", - "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", - "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.", - "accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen", - "hide-minicard-label-text": "Labeltext auf Minikarte ausblenden", - "show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen", - "assignee": "Zugewiesen", - "cardAssigneesPopup-title": "Zugewiesen", - "addmore-detail": "Eine detailliertere Beschreibung hinzufügen", - "show-on-card": "Zeige auf Karte", - "new": "Neu", - "editUserPopup-title": "Benutzer ändern", - "newUserPopup-title": "Neuer Benutzer", - "notifications": "Benachrichtigungen", - "view-all": "Alle anzeigen", - "filter-by-unread": "Nach Ungelesenen filtern", - "mark-all-as-read": "Als ungelesen markieren", - "allow-rename": "Umbenennen erlauben", - "allowRenamePopup-title": "Umbenennen erlauben" -} + "accept": "Akzeptieren", + "act-activity-notify": "Aktivitätsbenachrichtigung", + "act-addAttachment": "hat Anhang __attachment__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-deleteAttachment": "hat Anhang __attachment__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", + "act-addSubtask": "hat Teilaufgabe __subtask__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addedLabel": "hat Label __label__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-removeLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-removedLabel": "hat Label __label__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-addChecklist": "hat Checkliste __checklist__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-addChecklistItem": "hat Checklistenposition __checklistItem__ zu Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-removeChecklist": "hat Checkliste __checklist__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-removeChecklistItem": "hat Checklistenposition __checklistItem__ von Checkliste __checkList__ auf der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-checkedItem": "hat __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ abgehakt", + "act-uncheckedItem": "hat Haken von __checklistItem__ der Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-completeChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", + "act-uncompleteChecklist": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ unvervollständigt", + "act-addComment": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ kommentiert: __comment__", + "act-editComment": "hat den Kommentar auf Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", + "act-deleteComment": "hat den Kommentar von Karte __card__: __comment__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ gelöscht", + "act-createBoard": "hat Board __board__ erstellt", + "act-createSwimlane": "hat Swimlane __swimlane__ in Board __board__ erstellt", + "act-createCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ erstellt", + "act-createCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ angelegt", + "act-deleteCustomField": "hat benutzerdefiniertes Feld __customField__ in Board __board__ gelöscht", + "act-setCustomField": "hat benutzerdefiniertes Feld __customField__: __customFieldValue__ auf Karte __card__ auf Liste __list__ in Swimlane __swimlane__ in Board __board__ bearbeitet", + "act-createList": "hat Liste __list__ zu Board __board__ hinzugefügt", + "act-addBoardMember": "hat Mitglied __member__ zu Board __board__ hinzugefügt", + "act-archivedBoard": "hat Board __board__ ins Archiv verschoben", + "act-archivedCard": "hat Karte __card__ von der Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", + "act-archivedList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ ins Archiv verschoben", + "act-archivedSwimlane": "hat Swimlane __swimlane__ von Board __board__ ins Archiv verschoben", + "act-importBoard": "hat Board __board__ importiert", + "act-importCard": "hat Karte __card__ in Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", + "act-importList": "hat Liste __list__ in Swimlane __swimlane__ in Board __board__ importiert", + "act-joinMember": "hat Mitglied __member__ zur Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ hinzugefügt", + "act-moveCard": "hat Karte __card__ in Board __board__ von Liste __oldList__ in Swimlane __oldSwimlane__ zu Liste __list__ in Swimlane __swimlane__ verschoben", + "act-moveCardToOtherBoard": "hat Karte __card__ von Liste __oldList__ in Swimlane __oldSwimlane__ in Board __oldBoard__ zu Liste __list__ in Swimlane __swimlane__ in Board __board__ verschoben", + "act-removeBoardMember": "hat Mitglied __member__ von Board __board__ entfernt", + "act-restoredCard": "hat Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ wiederhergestellt", + "act-unjoinMember": "hat Mitglied __member__ von Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ entfernt", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Aktionen", + "activities": "Aktivitäten", + "activity": "Aktivität", + "activity-added": "hat %s zu %s hinzugefügt", + "activity-archived": "hat %s ins Archiv verschoben", + "activity-attached": "hat %s an %s angehängt", + "activity-created": "hat %s erstellt", + "activity-customfield-created": "hat das benutzerdefinierte Feld %s erstellt", + "activity-excluded": "hat %s von %s ausgeschlossen", + "activity-imported": "hat %s in %s von %s importiert", + "activity-imported-board": "hat %s von %s importiert", + "activity-joined": "ist %s beigetreten", + "activity-moved": "hat %s von %s nach %s verschoben", + "activity-on": "in %s", + "activity-removed": "hat %s von %s entfernt", + "activity-sent": "hat %s an %s gesendet", + "activity-unjoined": "hat %s verlassen", + "activity-subtask-added": "Teilaufgabe zu %s hinzugefügt", + "activity-checked-item": "markierte %s in Checkliste %s von %s", + "activity-unchecked-item": "hat %s in Checkliste %s von %s abgewählt", + "activity-checklist-added": "hat eine Checkliste zu %s hinzugefügt", + "activity-checklist-removed": "entfernte eine Checkliste von %s", + "activity-checklist-completed": "Abgeschlossene Checkliste", + "activity-checklist-uncompleted": "unvervollständigte die Checkliste %s von %s", + "activity-checklist-item-added": "hat ein Checklistenelement zu '%s' in %s hinzugefügt", + "activity-checklist-item-removed": "hat ein Checklistenelement von '%s' in %s entfernt", + "add": "Hinzufügen", + "activity-checked-item-card": "markiere %s in Checkliste %s", + "activity-unchecked-item-card": "hat %s in Checkliste %s abgewählt", + "activity-checklist-completed-card": "hat Checkliste __checklist__ der Karte __card__ auf der Liste __list__ in Swimlane __swimlane__ in Board __board__ vervollständigt", + "activity-checklist-uncompleted-card": "unvervollständigte die Checkliste %s", + "activity-editComment": "editierte Kommentar %s", + "activity-deleteComment": "löschte Kommentar %s", + "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", + "activity-startDate": "hat Startdatum zu %s geändert auf %s", + "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", + "activity-endDate": "hat Enddatum zu %s geändert auf %s", + "add-attachment": "Datei anhängen", + "add-board": "neues Board", + "add-template": "Vorlage hinzufügen", + "add-card": "Karte hinzufügen", + "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", + "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", + "add-swimlane": "Swimlane hinzufügen", + "add-subtask": "Teilaufgabe hinzufügen", + "add-checklist": "Checkliste hinzufügen", + "add-checklist-item": "Element zu Checkliste hinzufügen", + "add-cover": "Cover hinzufügen", + "add-label": "Label hinzufügen", + "add-list": "Liste hinzufügen", + "add-members": "Mitglieder hinzufügen", + "added": "Hinzugefügt", + "addMemberPopup-title": "Mitglieder", + "admin": "Admin", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", + "admin-announcement": "Ankündigung", + "admin-announcement-active": "Aktive systemweite Ankündigungen", + "admin-announcement-title": "Ankündigung des Administrators", + "all-boards": "Alle Boards", + "and-n-other-card": "und eine andere Karte", + "and-n-other-card_plural": "und __count__ andere Karten", + "apply": "Übernehmen", + "app-is-offline": "Laden, bitte warten. Das Aktualisieren der Seite führt zu Datenverlust. Wenn das Laden nicht funktioniert, überprüfen Sie bitte, ob der Server nicht angehalten wurde.", + "archive": "Ins Archiv verschieben", + "archive-all": "Alles ins Archiv verschieben", + "archive-board": "Board ins Archiv verschieben", + "archive-card": "Karte ins Archiv verschieben", + "archive-list": "Liste ins Archiv verschieben", + "archive-swimlane": "Swimlane ins Archiv verschieben", + "archive-selection": "Auswahl ins Archiv verschieben", + "archiveBoardPopup-title": "Board ins Archiv verschieben?", + "archived-items": "Archiv", + "archived-boards": "Boards im Archiv", + "restore-board": "Board wiederherstellen", + "no-archived-boards": "Keine Boards im Archiv.", + "archives": "Archiv", + "template": "Vorlage", + "templates": "Vorlagen", + "template-container": "Vorlagen-Container", + "add-template-container": "Vorlagen-Container hinzufügen", + "assign-member": "Mitglied zuweisen", + "attached": "angehängt", + "attachment": "Anhang", + "attachment-delete-pop": "Das Löschen eines Anhangs kann nicht rückgängig gemacht werden.", + "attachmentDeletePopup-title": "Anhang löschen?", + "attachments": "Anhänge", + "auto-watch": "Neue Boards nach Erstellung automatisch beobachten", + "avatar-too-big": "Das Profilbild ist zu groß (520KB max)", + "back": "Zurück", + "board-change-color": "Farbe ändern", + "board-nb-stars": "%s Sterne", + "board-not-found": "Board nicht gefunden", + "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", + "board-public-info": "Dieses Board wird <strong>öffentlich</strong> sein.", + "board-drag-drop-reorder-or-click-open": "Benutze Drag-and-Drop, um Board-Icons neu anzuordnen. Klicke auf ein Board-Icon, um das Board zu öffnen.", + "boardChangeColorPopup-title": "Farbe des Boards ändern", + "boardChangeTitlePopup-title": "Board umbenennen", + "boardChangeVisibilityPopup-title": "Sichtbarkeit ändern", + "boardChangeWatchPopup-title": "Beobachtung ändern", + "boardMenuPopup-title": "Boardeinstellungen", + "boardChangeViewPopup-title": "Boardansicht", + "boards": "Boards", + "board-view": "Boardansicht", + "board-view-cal": "Kalender", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Einklappen", + "board-view-gantt": "Gantt", + "board-view-lists": "Listen", + "bucket-example": "z.B. \"Löffelliste\"", + "cancel": "Abbrechen", + "card-archived": "Diese Karte wurde ins Archiv verschoben", + "board-archived": "Dieses Board wurde ins Archiv verschoben.", + "card-comments-title": "Diese Karte hat %s Kommentar(e).", + "card-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Alle Aktionen, die dieser Karte zugeordnet sind, werden ebenfalls gelöscht.", + "card-delete-pop": "Alle Aktionen werden aus dem Aktivitätsfeed entfernt und die Karte kann nicht wiedereröffnet werden. Die Aktion kann nicht rückgängig gemacht werden.", + "card-delete-suggest-archive": "Sie können eine Karte ins Archiv verschieben, um sie vom Board zu entfernen und die Aktivitäten zu behalten.", + "card-due": "Fällig", + "card-due-on": "Fällig am", + "card-spent": "Aufgewendete Zeit", + "card-edit-attachments": "Anhänge ändern", + "card-edit-custom-fields": "Benutzerdefinierte Felder editieren", + "card-edit-labels": "Labels ändern", + "card-edit-members": "Mitglieder ändern", + "card-labels-title": "Labels für diese Karte ändern.", + "card-members-title": "Der Karte Board-Mitglieder hinzufügen oder entfernen.", + "card-start": "Start", + "card-start-on": "Start am", + "cardAttachmentsPopup-title": "Anhängen von", + "cardCustomField-datePopup-title": "Datum ändern", + "cardCustomFieldsPopup-title": "Benutzerdefinierte Felder editieren", + "cardStartVotingPopup-title": "Abstimmung starten", + "positiveVoteMembersPopup-title": "Befürworter", + "negativeVoteMembersPopup-title": "Gegner", + "card-edit-voting": "Abstimmung editieren", + "editVoteEndDatePopup-title": "Enddatum der Abstimmung ändern", + "allowNonBoardMembers": "Alle eingeloggte Nutzer erlauben", + "vote-question": "Abstimmen über", + "vote-public": "Zeigen, wer was gewählt hat", + "vote-for-it": "Dafür", + "vote-against": "Dagegen", + "deleteVotePopup-title": "Wahl löschen?", + "vote-delete-pop": "Löschen ist unwiderruflich. Alle Aktionen die dieser Karte zugeordnet sind werden ebenfalls gelöscht.", + "cardStartPlanningPokerPopup-title": "Planungspoker starten", + "card-edit-planning-poker": "Planungspoker ändern", + "editPokerEndDatePopup-title": "Enddatum für Planungspoker-Stimme ändern", + "poker-question": "Planungspoker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Beenden", + "poker-result-votes": "Werte", + "poker-result-who": "Wer", + "poker-replay": "Wiederholen", + "set-estimation": "Schätzung vornehmen", + "deletePokerPopup-title": "Planungspoker löschen?", + "poker-delete-pop": "Die Löschung ist permanent. Sie werden alles im Zusammenhang mit diesem Planungspoker verlieren.", + "cardDeletePopup-title": "Karte löschen?", + "cardDetailsActionsPopup-title": "Kartenaktionen", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Mitglieder", + "cardMorePopup-title": "Mehr", + "cardTemplatePopup-title": "Vorlage erstellen", + "cards": "Karten", + "cards-count": "Karten", + "cards-count-one": "Karte", + "casSignIn": "Mit CAS anmelden", + "cardType-card": "Karte", + "cardType-linkedCard": "Verknüpfte Karte", + "cardType-linkedBoard": "Verknüpftes Board", + "change": "Ändern", + "change-avatar": "Profilbild ändern", + "change-password": "Passwort ändern", + "change-permissions": "Berechtigungen ändern", + "change-settings": "Einstellungen ändern", + "changeAvatarPopup-title": "Profilbild ändern", + "changeLanguagePopup-title": "Sprache ändern", + "changePasswordPopup-title": "Passwort ändern", + "changePermissionsPopup-title": "Berechtigungen ändern", + "changeSettingsPopup-title": "Einstellungen ändern", + "subtasks": "Teilaufgaben", + "checklists": "Checklisten", + "click-to-star": "Klicken Sie, um das Board mit einem Stern zu markieren.", + "click-to-unstar": "Klicken Sie, um den Stern vom Board zu entfernen.", + "clipboard": "Zwischenablage oder Drag & Drop", + "close": "Schließen", + "close-board": "Board schließen", + "close-board-pop": "Sie können das Board wiederherstellen, indem Sie die Schaltfläche \"Archiv\" in der Kopfzeile der Startseite anklicken.", + "close-card": "Karte schließen", + "color-black": "schwarz", + "color-blue": "blau", + "color-crimson": "Karminrot", + "color-darkgreen": "Dunkelgrün", + "color-gold": "Gold", + "color-gray": "Grau", + "color-green": "grün", + "color-indigo": "Indigo", + "color-lime": "hellgrün", + "color-magenta": "Magentarot", + "color-mistyrose": "Altrosa", + "color-navy": "Marineblau", + "color-orange": "orange", + "color-paleturquoise": "Blasses Türkis", + "color-peachpuff": "Pfirsich", + "color-pink": "pink", + "color-plum": "Pflaume", + "color-purple": "lila", + "color-red": "rot", + "color-saddlebrown": "Sattelbraun", + "color-silver": "Silber", + "color-sky": "himmelblau", + "color-slateblue": "Schieferblau", + "color-white": "Weiß", + "color-yellow": "gelb", + "unset-color": "Nicht festgelegt", + "comment": "Kommentar", + "comment-placeholder": "Kommentar schreiben", + "comment-only": "Nur Kommentare", + "comment-only-desc": "Kann Karten nur kommentieren.", + "no-comments": "Keine Kommentare", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", + "worker": "Arbeiter", + "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?", + "confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?", + "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", + "linkCardPopup-title": "Karte verknüpfen", + "searchElementPopup-title": "Suche", + "copyCardPopup-title": "Karte kopieren", + "copyChecklistToManyCardsPopup-title": "Checklistenvorlage in mehrere Karten kopieren", + "copyChecklistToManyCardsPopup-instructions": "Titel und Beschreibungen der Zielkarten im folgenden JSON-Format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", + "create": "Erstellen", + "createBoardPopup-title": "Board erstellen", + "chooseBoardSourcePopup-title": "Board importieren", + "createLabelPopup-title": "Label erstellen", + "createCustomField": "Feld erstellen", + "createCustomFieldPopup-title": "Feld erstellen", + "current": "aktuell", + "custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.", + "custom-field-checkbox": "Kontrollkästchen", + "custom-field-currency": "Währung", + "custom-field-currency-option": "Währungszeichen", + "custom-field-date": "Datum", + "custom-field-dropdown": "Dropdownliste", + "custom-field-dropdown-none": "(keiner)", + "custom-field-dropdown-options": "Listenoptionen", + "custom-field-dropdown-options-placeholder": "Drücken Sie die Eingabetaste, um weitere Optionen hinzuzufügen", + "custom-field-dropdown-unknown": "(unbekannt)", + "custom-field-number": "Zahl", + "custom-field-text": "Text", + "custom-fields": "Benutzerdefinierte Felder", + "date": "Datum", + "decline": "Ablehnen", + "default-avatar": "Standard Profilbild", + "delete": "Löschen", + "deleteCustomFieldPopup-title": "Benutzerdefiniertes Feld löschen?", + "deleteLabelPopup-title": "Label löschen?", + "description": "Beschreibung", + "disambiguateMultiLabelPopup-title": "Labels vereinheitlichen", + "disambiguateMultiMemberPopup-title": "Mitglieder vereinheitlichen", + "discard": "Verwerfen", + "done": "Erledigt", + "download": "Herunterladen", + "edit": "Bearbeiten", + "edit-avatar": "Profilbild ändern", + "edit-profile": "Profil ändern", + "edit-wip-limit": "WIP-Limit bearbeiten", + "soft-wip-limit": "Soft WIP-Limit", + "editCardStartDatePopup-title": "Startdatum ändern", + "editCardDueDatePopup-title": "Fälligkeitsdatum ändern", + "editCustomFieldPopup-title": "Feld bearbeiten", + "editCardSpentTimePopup-title": "Aufgewendete Zeit ändern", + "editLabelPopup-title": "Label ändern", + "editNotificationPopup-title": "Benachrichtigung ändern", + "editProfilePopup-title": "Profil ändern", + "email": "E-Mail", + "email-enrollAccount-subject": "Ihr Benutzerkonto auf __siteName__ wurde erstellt", + "email-enrollAccount-text": "Hallo __user__,\n\num den Dienst nutzen zu können, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "email-fail": "Senden der E-Mail fehlgeschlagen", + "email-fail-text": "Fehler beim Senden der E-Mail", + "email-invalid": "Ungültige E-Mail-Adresse", + "email-invite": "per E-Mail einladen", + "email-invite-subject": "__inviter__ hat Ihnen eine Einladung geschickt", + "email-invite-text": "Hallo __user__,\n\n__inviter__ hat Sie zu dem Board \"__board__\" eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n\n__url__\n\nDanke.", + "email-resetPassword-subject": "Setzten Sie ihr Passwort auf __siteName__ zurück", + "email-resetPassword-text": "Hallo __user__,\n\num ihr Passwort zurückzusetzen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "email-sent": "E-Mail gesendet", + "email-verifyEmail-subject": "Bestätigen Sie ihre E-Mail-Adresse auf __siteName__", + "email-verifyEmail-text": "Hallo __user__,\n\num ihre E-Mail-Adresse zu bestätigen, klicken Sie bitte auf folgenden Link:\n\n__url__\n\nDanke.", + "enable-wip-limit": "WIP-Limit einschalten", + "error-board-doesNotExist": "Dieses Board existiert nicht", + "error-board-notAdmin": "Um das zu tun, müssen Sie Administrator dieses Boards sein", + "error-board-notAMember": "Um das zu tun, müssen Sie Mitglied dieses Boards sein", + "error-json-malformed": "Ihre Eingabe ist kein gültiges JSON", + "error-json-schema": "Ihre JSON-Daten enthalten nicht die gewünschten Informationen im richtigen Format", + "error-csv-schema": "hre CSV (Comma Separated Values)/TSV (Tab Separated Values) enthalten nicht die gewünschten Informationen im richtigen Format", + "error-list-doesNotExist": "Diese Liste existiert nicht", + "error-user-doesNotExist": "Dieser Nutzer existiert nicht", + "error-user-notAllowSelf": "Sie können sich nicht selbst einladen.", + "error-user-notCreated": "Dieser Nutzer ist nicht angelegt", + "error-username-taken": "Dieser Benutzername ist bereits vergeben", + "error-orgname-taken": "Dieser Organisationsname ist schon vergeben", + "error-teamname-taken": "Dieser Teamname ist schon vergeben", + "error-email-taken": "E-Mail wird schon verwendet", + "export-board": "Board exportieren", + "export-board-json": "Board als JSON exportieren", + "export-board-csv": "Board als CSV exportieren", + "export-board-tsv": "Board als TSV exportieren", + "export-board-excel": "Board nach Excel exportieren", + "user-can-not-export-excel": "Benutzer kann nicht nach Excel exportieren", + "export-board-html": "Board als HTML exportieren", + "export-card": "Karte exportieren", + "export-card-pdf": "Karte in ein PDF exportieren", + "user-can-not-export-card-to-pdf": "Benutzer kann Karte nicht in ein PDF exportieren", + "exportBoardPopup-title": "Board exportieren", + "exportCardPopup-title": "Karte exportieren", + "sort": "Sortieren", + "sort-desc": "Zum Sortieren der Liste klicken", + "list-sort-by": "Sortieren der Liste nach:", + "list-label-modifiedAt": "Letzte Zugriffszeit", + "list-label-title": "Name der Liste", + "list-label-sort": "Ihre manuelle Sortierung", + "list-label-short-modifiedAt": "(Z)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Karten oder Listen filtern", + "filter-dates-label": "Nach Datum filtern", + "filter-no-due-date": "Kein Fälligkeitsdatum", + "filter-overdue": "Überfällig", + "filter-due-today": "Heute fällig", + "filter-due-this-week": "Diese Woche fällig", + "filter-due-tomorrow": "Morgen fällig", + "list-filter-label": "Liste nach Titel filtern", + "filter-clear": "Filter entfernen", + "filter-labels-label": "Nach Label filtern", + "filter-no-label": "Kein Label", + "filter-member-label": "Nach Nutzer filtern", + "filter-no-member": "Kein Mitglied", + "filter-assignee-label": "Nach Zuordnung filtern", + "filter-no-assignee": "Nicht zugewiesen", + "filter-custom-fields-label": "Filtern nach benutzerdefinierten Feldern", + "filter-no-custom-fields": "Keine benutzerdefinierten Felder", + "filter-show-archive": "Archivierte Listen anzeigen", + "filter-hide-empty": "Leere Listen verstecken", + "filter-on": "Filter ist aktiv", + "filter-on-desc": "Sie filtern die Karten in diesem Board. Klicken Sie, um den Filter zu bearbeiten.", + "filter-to-selection": "Ergebnisse auswählen", + "other-filters-label": "Andere Filter", + "advanced-filter-label": "Erweiterter Filter", + "advanced-filter-description": "Der erweiterte Filter erlaubt die Eingabe von Zeichenfolgen, die folgende Operatoren enthalten: == != <= >= && || ( ). Ein Leerzeichen wird als Trennzeichen zwischen den Operatoren verwendet. Sie können nach allen benutzerdefinierten Feldern filtern, indem Sie deren Namen und Werte eingeben. Zum Beispiel: Feld1 == Wert1. Hinweis: Wenn Felder oder Werte Leerzeichen enthalten, müssen Sie sie in einfache Anführungszeichen setzen. Zum Beispiel: 'Feld 1' == 'Wert 1'. Um einzelne Steuerzeichen (' \\/) zu überspringen, können Sie \\ verwenden. Zum Beispiel: Feld1 == Ich bin\\'s. Sie können außerdem mehrere Bedingungen kombinieren. Zum Beispiel: F1 == W1 || F1 == W2. Normalerweise werden alle Operatoren von links nach rechts interpretiert. Sie können die Reihenfolge ändern, indem Sie Klammern setzen. Zum Beispiel: F1 == W1 && ( F2 == W2 || F2 == W3 ). Sie können Textfelder auch mithilfe regulärer Ausdrücke durchsuchen: F1 == /Tes.*/i", + "fullname": "Vollständiger Name", + "header-logo-title": "Zurück zur Board Seite.", + "hide-system-messages": "Systemmeldungen ausblenden", + "headerBarCreateBoardPopup-title": "Board erstellen", + "home": "Home", + "import": "Importieren", + "impersonate-user": "als Benutzer ausgeben", + "link": "Verknüpfung", + "import-board": "Board importieren", + "import-board-c": "Board importieren", + "import-board-title-trello": "Board von Trello importieren", + "import-board-title-wekan": "Board aus vorherigem Export importieren", + "import-board-title-csv": "Board von CSV/TSV importieren", + "from-trello": "Von Trello", + "from-wekan": "Aus vorherigem Export", + "from-csv": "Aus CSV/TSV", + "import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text", + "import-board-instruction-csv": "Fügen Sie die Ihre Comma-Separated- (CSV) / bzw. Tab-Separated-Values (TSV) ein.", + "import-board-instruction-wekan": "Gehen Sie in Ihrem Board auf 'Menü', danach auf 'Board exportieren' und kopieren Sie den Text aus der heruntergeladenen Datei.", + "import-board-instruction-about-errors": "Treten beim importieren eines Board Fehler auf, so kann der Import dennoch erfolgreich abgeschlossen sein und das Board ist auf der Seite \"Alle Boards\" zusehen.", + "import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein", + "import-csv-placeholder": "Fügen Sie die korrekten CSV/TSV-Daten hier ein ", + "import-map-members": "Mitglieder zuordnen", + "import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen sie die Mitglieder, die Sie importieren wollen, Ihren Benutzern zu.", + "import-members-map-note": "Anmerkung: Nicht zugeordnete Mitglieder werden dem aktuellen Benutzer zugeordnet.", + "import-show-user-mapping": "Mitgliederzuordnung überprüfen", + "import-user-select": "Wählen Sie den bestehenden Benutzer aus, den Sie für dieses Mitglied verwenden wollen.", + "importMapMembersAddPopup-title": "Mitglied auswählen", + "info": "Version", + "initials": "Initialen", + "invalid-date": "Ungültiges Datum", + "invalid-time": "Ungültige Zeitangabe", + "invalid-user": "Ungültiger Benutzer", + "joined": "beigetreten", + "just-invited": "Sie wurden soeben zu diesem Board eingeladen", + "keyboard-shortcuts": "Tastaturkürzel", + "label-create": "Label erstellen", + "label-default": "%s Label (Standard)", + "label-delete-pop": "Aktion kann nicht rückgängig gemacht werden. Das Label wird von allen Karten entfernt und seine Historie gelöscht.", + "labels": "Labels", + "language": "Sprache", + "last-admin-desc": "Sie können keine Rollen ändern, weil es mindestens einen Administrator geben muss.", + "leave-board": "Board verlassen", + "leave-board-pop": "Sind Sie sicher, dass Sie __boardTitle__ verlassen möchten? Sie werden von allen Karten in diesem Board entfernt.", + "leaveBoardPopup-title": "Board verlassen?", + "link-card": "Link zu dieser Karte", + "list-archive-cards": "Alle Karten dieser Liste ins Archiv verschieben", + "list-archive-cards-pop": "Alle Karten dieser Liste werden vom Board entfernt. Um Karten im Papierkorb anzuzeigen und wiederherzustellen, klicken Sie auf \"Menü\" > \"Archiv\".", + "list-move-cards": "Alle Karten in dieser Liste verschieben", + "list-select-cards": "Alle Karten in dieser Liste auswählen", + "set-color-list": "Lege Farbe fest", + "listActionPopup-title": "Listenaktionen", + "settingsUserPopup-title": "Benutzereinstellungen", + "settingsTeamPopup-title": "Team-Einstellungen", + "settingsOrgPopup-title": "Organisations-Einstellungen", + "swimlaneActionPopup-title": "Swimlaneaktionen", + "swimlaneAddPopup-title": "Swimlane unterhalb einfügen", + "listImportCardPopup-title": "Eine Trello-Karte importieren", + "listImportCardsTsvPopup-title": "CSV/TSV importieren", + "listMorePopup-title": "Mehr", + "link-list": "Link zu dieser Liste", + "list-delete-pop": "Alle Aktionen werden aus dem Verlauf gelöscht und die Liste kann nicht wiederhergestellt werden.", + "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", + "lists": "Listen", + "swimlanes": "Swimlanes", + "log-out": "Ausloggen", + "log-in": "Einloggen", + "loginPopup-title": "Einloggen", + "memberMenuPopup-title": "Nutzereinstellungen", + "members": "Mitglieder", + "menu": "Menü", + "move-selection": "Auswahl verschieben", + "moveCardPopup-title": "Karte verschieben", + "moveCardToBottom-title": "Ans Ende verschieben", + "moveCardToTop-title": "Zum Anfang verschieben", + "moveSelectionPopup-title": "Auswahl verschieben", + "multi-selection": "Mehrfachauswahl", + "multi-selection-label": "Label für die Auswahl setzen", + "multi-selection-member": "Mitglied für die Auswahl setzen", + "multi-selection-on": "Mehrfachauswahl ist aktiv", + "muted": "Stumm", + "muted-info": "Sie werden nicht über Änderungen auf diesem Board benachrichtigt", + "my-boards": "Meine Boards", + "name": "Name", + "no-archived-cards": "Keine Karten im Archiv.", + "no-archived-lists": "Keine Listen im Archiv.", + "no-archived-swimlanes": "Keine Swimlanes im Archiv.", + "no-results": "Keine Ergebnisse", + "normal": "Normal", + "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", + "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", + "notify-participate": "Benachrichtigungen zu allen Karten erhalten, an denen Sie teilnehmen", + "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", + "optional": "optional", + "or": "oder", + "page-maybe-private": "Diese Seite könnte privat sein. Vielleicht können Sie sie sehen, wenn Sie sich <a href='%s'>einloggen</a>.", + "page-not-found": "Seite nicht gefunden.", + "password": "Passwort", + "paste-or-dragdrop": "Einfügen oder Datei mit Drag & Drop ablegen (nur Bilder)", + "participating": "Teilnehmen", + "preview": "Vorschau", + "previewAttachedImagePopup-title": "Vorschau", + "previewClipboardImagePopup-title": "Vorschau", + "private": "Privat", + "private-desc": "Dieses Board ist privat. Nur Nutzer, die zu dem Board gehören, können es anschauen und bearbeiten.", + "profile": "Profil", + "public": "Öffentlich", + "public-desc": "Dieses Board ist öffentlich. Es ist für jeden, der den Link kennt, sichtbar und taucht in Suchmaschinen wie Google auf. Nur Nutzer, die zum Board hinzugefügt wurden, können es bearbeiten.", + "quick-access-description": "Markieren Sie ein Board mit einem Stern, um dieser Leiste eine Verknüpfung hinzuzufügen.", + "remove-cover": "Cover entfernen", + "remove-from-board": "Von Board entfernen", + "remove-label": "Label entfernen", + "listDeletePopup-title": "Liste löschen?", + "remove-member": "Nutzer entfernen", + "remove-member-from-card": "Von Karte entfernen", + "remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.", + "removeMemberPopup-title": "Mitglied entfernen?", + "rename": "Umbenennen", + "rename-board": "Board umbenennen", + "restore": "Wiederherstellen", + "save": "Speichern", + "search": "Suchen", + "rules": "Regeln", + "search-cards": "Suche nach Karten-/Listentiteln, Beschreibungen und personalisierten Feldern auf diesem Brett ", + "search-example": "Suchtext eingeben und Enter drücken", + "select-color": "Farbe auswählen", + "select-board": "Board auswählen", + "set-wip-limit-value": "Setzen Sie ein Limit für die maximale Anzahl von Aufgaben in dieser Liste", + "setWipLimitPopup-title": "WIP-Limit setzen", + "shortcut-assign-self": "Fügen Sie sich zur aktuellen Karte hinzu", + "shortcut-autocomplete-emoji": "Emojis vervollständigen", + "shortcut-autocomplete-members": "Mitglieder vervollständigen", + "shortcut-clear-filters": "Alle Filter entfernen", + "shortcut-close-dialog": "Dialog schließen", + "shortcut-filter-my-cards": "Meine Karten filtern", + "shortcut-show-shortcuts": "Liste der Tastaturkürzel anzeigen", + "shortcut-toggle-filterbar": "Filter-Seitenleiste ein-/ausblenden", + "shortcut-toggle-searchbar": "Such-Seitenleiste ein-/ausblenden", + "shortcut-toggle-sidebar": "Seitenleiste ein-/ausblenden", + "show-cards-minimum-count": "Zeigt die Kartenanzahl an, wenn die Liste mehr enthält als", + "sidebar-open": "Seitenleiste öffnen", + "sidebar-close": "Seitenleiste schließen", + "signupPopup-title": "Benutzerkonto erstellen", + "star-board-title": "Klicken Sie, um das Board mit einem Stern zu markieren. Es erscheint dann oben in ihrer Boardliste.", + "starred-boards": "Markierte Boards", + "starred-boards-description": "Markierte Boards erscheinen oben in ihrer Boardliste.", + "subscribe": "Abonnieren", + "team": "Team", + "this-board": "diesem Board", + "this-card": "diese Karte", + "spent-time-hours": "Aufgewendete Zeit (Stunden)", + "overtime-hours": "Mehrarbeit (Stunden)", + "overtime": "Mehrarbeit", + "has-overtime-cards": "Hat Karten mit Mehrarbeit", + "has-spenttime-cards": "Hat Karten mit aufgewendeten Zeiten", + "time": "Zeit", + "title": "Titel", + "tracking": "Folgen", + "tracking-info": "Sie werden über alle Änderungen an Karten benachrichtigt, an denen Sie als Ersteller oder Mitglied beteiligt sind.", + "type": "Typ", + "unassign-member": "Mitglied entfernen", + "unsaved-description": "Sie haben eine nicht gespeicherte Änderung.", + "unwatch": "Beobachtung entfernen", + "upload": "Upload", + "upload-avatar": "Profilbild hochladen", + "uploaded-avatar": "Profilbild hochgeladen", + "custom-top-left-corner-logo-image-url": "Benutzerdefiniertes Logo oben links Bild URL", + "custom-top-left-corner-logo-link-url": "Benutzerdefiniertes Logo oben links Link URL", + "custom-top-left-corner-logo-height": "Benutzerdefiniertes Logo oben links Höhe. Voreinstellung: 27", + "custom-login-logo-image-url": "Benutzerdefiniertes Login Logo Bild URL", + "custom-login-logo-link-url": "Benutzerdefiniertes Login Logo Link URL", + "text-below-custom-login-logo": "Text unterhalb benutzerdefiniertem Login Logo", + "automatic-linked-url-schemes": "Spezielle URL-Schemas, die durch Klick automatisch öffenbar sein sollen. Ein URL-Schema pro Zeile", + "username": "Benutzername", + "import-usernames": "Nutzernamen importieren", + "view-it": "Ansehen", + "warn-list-archived": "Warnung: Diese Karte befindet sich in einer Liste im Archiv", + "watch": "Beobachten", + "watching": "Beobachten", + "watching-info": "Sie werden über alle Änderungen in diesem Board benachrichtigt", + "welcome-board": "Willkommen-Board", + "welcome-swimlane": "Meilenstein 1", + "welcome-list1": "Grundlagen", + "welcome-list2": "Fortgeschritten", + "card-templates-swimlane": "Kartenvorlagen", + "list-templates-swimlane": "Listenvorlagen", + "board-templates-swimlane": "Boardvorlagen", + "what-to-do": "Was wollen Sie tun?", + "wipLimitErrorPopup-title": "Ungültiges WIP-Limit", + "wipLimitErrorPopup-dialog-pt1": "Die Anzahl von Aufgaben in dieser Liste ist größer als das von Ihnen definierte WIP-Limit.", + "wipLimitErrorPopup-dialog-pt2": "Bitte verschieben Sie einige Aufgaben aus dieser Liste oder setzen Sie ein grösseres WIP-Limit.", + "admin-panel": "Administration", + "settings": "Einstellungen", + "people": "Nutzer", + "registration": "Registrierung", + "disable-self-registration": "Selbstregistrierung deaktivieren", + "invite": "Einladen", + "invite-people": "Nutzer einladen", + "to-boards": "In Board(s)", + "email-addresses": "E-Mail Adressen", + "smtp-host-description": "Die Adresse Ihres SMTP-Servers für ausgehende E-Mails.", + "smtp-port-description": "Der Port Ihres SMTP-Servers für ausgehende E-Mails.", + "smtp-tls-description": "Aktiviere TLS Unterstützung für SMTP Server", + "smtp-host": "SMTP-Server", + "smtp-port": "SMTP-Port", + "smtp-username": "Benutzername", + "smtp-password": "Passwort", + "smtp-tls": "TLS Unterstützung", + "send-from": "Absender", + "send-smtp-test": "Test-E-Mail an sich selbst schicken", + "invitation-code": "Einladungscode", + "email-invite-register-subject": "__inviter__ hat Ihnen eine Einladung geschickt", + "email-invite-register-text": "Sehr geehrte(r) __user__,\n\n__inviter__ hat Sie zur Mitarbeit an einem Kanbanboard eingeladen.\n\nBitte klicken Sie auf folgenden Link:\n__url__\n\nIhr Einladungscode lautet: __icode__\n\nDanke.", + "email-smtp-test-subject": "SMTP Test-E-Mail", + "email-smtp-test-text": "Sie haben erfolgreich eine E-Mail versandt", + "error-invitation-code-not-exist": "Ungültiger Einladungscode", + "error-notAuthorized": "Sie sind nicht berechtigt diese Seite zu sehen.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional für Authentifizierung)", + "outgoing-webhooks": "Ausgehende Webhooks", + "bidirectional-webhooks": "Zwei-Wege Webhooks", + "outgoingWebhooksPopup-title": "Ausgehende Webhooks", + "boardCardTitlePopup-title": "Kartentitelfilter", + "disable-webhook": "Diesen Webhook deaktivieren", + "global-webhook": "Globale Webhooks", + "new-outgoing-webhook": "Neuer ausgehender Webhook", + "no-name": "(Unbekannt)", + "Node_version": "Node-Version", + "Meteor_version": "Meteor-Version", + "MongoDB_version": "MongoDB-Version", + "MongoDB_storage_engine": "MongoDB-Speicher-Engine", + "MongoDB_Oplog_enabled": "MongoDB-Oplog aktiviert", + "OS_Arch": "Betriebssystem-Architektur", + "OS_Cpus": "Anzahl Prozessoren", + "OS_Freemem": "Freier Arbeitsspeicher", + "OS_Loadavg": "Mittlere Systembelastung", + "OS_Platform": "Plattform", + "OS_Release": "Version des Betriebssystem", + "OS_Totalmem": "Gesamter Arbeitsspeicher", + "OS_Type": "Typ des Betriebssystems", + "OS_Uptime": "Laufzeit des Systems", + "days": "Tage", + "hours": "Stunden", + "minutes": "Minuten", + "seconds": "Sekunden", + "show-field-on-card": "Zeige dieses Feld auf der Karte", + "automatically-field-on-card": "Füge Feld neuen Karten hinzu", + "always-field-on-card": "Füge Feld allen Karten hinzu", + "showLabel-field-on-card": "Feldbezeichnung auf Minikarte anzeigen", + "yes": "Ja", + "no": "Nein", + "accounts": "Konten", + "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", + "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", + "createdAt": "Erstellt am", + "modifiedAt": "Geändert am", + "verified": "Geprüft", + "active": "Aktiv", + "card-received": "Empfangen", + "card-received-on": "Empfangen am", + "card-end": "Ende", + "card-end-on": "Endet am", + "editCardReceivedDatePopup-title": "Empfangsdatum ändern", + "editCardEndDatePopup-title": "Enddatum ändern", + "setCardColorPopup-title": "Farbe festlegen", + "setCardActionsColorPopup-title": "Farbe wählen", + "setSwimlaneColorPopup-title": "Farbe wählen", + "setListColorPopup-title": "Farbe wählen", + "assigned-by": "Zugewiesen von", + "requested-by": "Angefordert von", + "card-sorting-by-number": "Kartensortierung nach Nummer", + "board-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Sie werden alle Listen, Karten und Aktionen, die mit diesem Board verbunden sind, verlieren.", + "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", + "boardDeletePopup-title": "Board löschen?", + "delete-board": "Board löschen", + "default-subtasks-board": "Teilaufgabe für __board__ Board", + "default": "Standard", + "queue": "Warteschlange", + "subtask-settings": "Einstellungen für Teilaufgaben", + "card-settings": "Karten-Einstellungen", + "boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben", + "boardCardSettingsPopup-title": "Karten-Einstellungen", + "deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:", + "deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:", + "show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:", + "prefix-with-full-path": "Vollständiger Pfad über Titel", + "prefix-with-parent": "Über Titel", + "subtext-with-full-path": "Vollständiger Pfad unter Titel", + "subtext-with-parent": "Unter Titel", + "change-card-parent": "Übergeordnete Karte ändern", + "parent-card": "Übergeordnete Karte", + "source-board": "Quellboard", + "no-parent": "Nicht anzeigen", + "activity-added-label": "fügte Label '%s' zu %s hinzu", + "activity-removed-label": "entfernte Label '%s' von %s", + "activity-delete-attach": "löschte ein Anhang von %s", + "activity-added-label-card": "Label hinzugefügt '%s'", + "activity-removed-label-card": "Label entfernt '%s'", + "activity-delete-attach-card": "hat einen Anhang gelöscht", + "activity-set-customfield": "setze benutzerdefiniertes Feld '%s' zu '%s' in %s", + "activity-unset-customfield": "entferne benutzerdefiniertes Feld '%s' in %s", + "r-rule": "Regel", + "r-add-trigger": "Auslöser hinzufügen", + "r-add-action": "Aktion hinzufügen", + "r-board-rules": "Boardregeln", + "r-add-rule": "Regel hinzufügen", + "r-view-rule": "Regel anzeigen", + "r-delete-rule": "Regel löschen", + "r-new-rule-name": "Neuer Regeltitel", + "r-no-rules": "Keine Regeln", + "r-trigger": "Auslöser", + "r-action": "Aktion", + "r-when-a-card": "Wenn Karte", + "r-is": "wird", + "r-is-moved": "verschoben wird", + "r-added-to": "Hinzugefügt zu", + "r-removed-from": "entfernt von", + "r-the-board": "das Board", + "r-list": "Liste", + "list": "Liste", + "set-filter": "Setze Filter", + "r-moved-to": "verschoben nach", + "r-moved-from": "verschoben von", + "r-archived": "ins Archiv verschoben", + "r-unarchived": "aus dem Archiv wiederhergestellt", + "r-a-card": "einer Karte", + "r-when-a-label-is": "Wenn ein Label", + "r-when-the-label": "Wenn das Label", + "r-list-name": "Listenname", + "r-when-a-member": "Wenn ein Mitglied", + "r-when-the-member": "Wenn das Mitglied", + "r-name": "Name", + "r-when-a-attach": "Wenn ein Anhang", + "r-when-a-checklist": "Wenn eine Checkliste wird", + "r-when-the-checklist": "Wenn die Checkliste", + "r-completed": "abgeschlossen", + "r-made-incomplete": "unvollständig gemacht", + "r-when-a-item": "Wenn eine Checklistenposition", + "r-when-the-item": "Wenn die Checklistenposition", + "r-checked": "markiert wird", + "r-unchecked": "abgewählt wird", + "r-move-card-to": "Verschiebe Karte an", + "r-top-of": "Anfang von", + "r-bottom-of": "Ende von", + "r-its-list": "seiner Liste", + "r-archive": "Ins Archiv verschieben", + "r-unarchive": "Aus dem Archiv wiederherstellen", + "r-card": "Karte", + "r-add": "Hinzufügen", + "r-remove": "entfernen", + "r-label": "Label", + "r-member": "Mitglied", + "r-remove-all": "Entferne alle Mitglieder von der Karte", + "r-set-color": "Farbe festlegen auf", + "r-checklist": "Checkliste", + "r-check-all": "Alle markieren", + "r-uncheck-all": "Alle abwählen", + "r-items-check": "Elemente der Checkliste", + "r-check": "Markieren", + "r-uncheck": "Abwählen", + "r-item": "Element", + "r-of-checklist": "der Checkliste", + "r-send-email": "Eine E-Mail senden", + "r-to": "an", + "r-of": "von", + "r-subject": "Betreff", + "r-rule-details": "Regeldetails", + "r-d-move-to-top-gen": "Karte nach oben in die Liste verschieben", + "r-d-move-to-top-spec": "Karte an den Anfang der Liste verschieben", + "r-d-move-to-bottom-gen": "Karte nach unten in die Liste verschieben", + "r-d-move-to-bottom-spec": "Karte an das Ende der Liste verschieben", + "r-d-send-email": "E-Mail senden", + "r-d-send-email-to": "an", + "r-d-send-email-subject": "Betreff", + "r-d-send-email-message": "Nachricht", + "r-d-archive": "Karte ins Archiv verschieben", + "r-d-unarchive": "Karte aus dem Archiv wiederherstellen", + "r-d-add-label": "Label hinzufügen", + "r-d-remove-label": "Label entfernen", + "r-create-card": "Neue Karte erstellen", + "r-in-list": "in der Liste", + "r-in-swimlane": "in Swimlane", + "r-d-add-member": "Mitglied hinzufügen", + "r-d-remove-member": "Mitglied entfernen", + "r-d-remove-all-member": "Entferne alle Mitglieder", + "r-d-check-all": "Alle Elemente der Liste markieren", + "r-d-uncheck-all": "Alle Element der Liste abwählen", + "r-d-check-one": "Element auswählen", + "r-d-uncheck-one": "Element abwählen", + "r-d-check-of-list": "der Checkliste", + "r-d-add-checklist": "Checkliste hinzufügen", + "r-d-remove-checklist": "Checkliste entfernen", + "r-by": "durch", + "r-add-checklist": "Checkliste hinzufügen", + "r-with-items": "mit Elementen", + "r-items-list": "Element1,Element2,Element3", + "r-add-swimlane": "Füge Swimlane hinzu", + "r-swimlane-name": "Swimlanename", + "r-board-note": "Hinweis: Lassen Sie ein Feld leer, um alle möglichen Werte zu finden.", + "r-checklist-note": "Hinweis: Die Elemente der Checkliste müssen als kommagetrennte Werte geschrieben werden.", + "r-when-a-card-is-moved": "Wenn eine Karte in eine andere Liste verschoben wird", + "r-set": "Setze", + "r-update": "Aktualisiere", + "r-datefield": "Datumsfeld", + "r-df-start-at": "Start", + "r-df-due-at": "Fällig", + "r-df-end-at": "Ende", + "r-df-received-at": "Empfangen", + "r-to-current-datetime": "auf das aktuelle Datum/Zeit", + "r-remove-value-from": "Entferne Wert von", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentifizierungsmethode", + "authentication-type": "Authentifizierungstyp", + "custom-product-name": "Benutzerdefinierter Produktname", + "layout": "Layout", + "hide-logo": "Verstecke Logo", + "add-custom-html-after-body-start": "Füge benutzerdefiniertes HTML nach <body> Anfang hinzu", + "add-custom-html-before-body-end": "Füge benutzerdefiniertes HTML vor </body>Ende hinzu", + "error-undefined": "Etwas ist schief gelaufen", + "error-ldap-login": "Es ist ein Fehler beim Anmelden aufgetreten", + "display-authentication-method": "Anzeige Authentifizierungsverfahren", + "default-authentication-method": "Standardauthentifizierungsverfahren", + "duplicate-board": "Board duplizieren", + "org-number": "Die Anzahl an Organisationen ist:", + "team-number": "Die Anzahl an Teams ist:", + "people-number": "Anzahl der Personen:", + "swimlaneDeletePopup-title": "Swimlane löschen?", + "swimlane-delete-pop": "Alle Aktionen werden aus dem Aktivitätenfeed entfernt und die Swimlane kann nicht wiederhergestellt werden. Die Aktion kann nicht rückgängig gemacht werden.", + "restore-all": "Alles wiederherstellen", + "delete-all": "Alles löschen", + "loading": "Laden, bitte warten.", + "previous_as": "letzter Zeitpunkt war", + "act-a-dueAt": "hat Fälligkeit geändert auf\nWann: __timeValue__\nWo: __card__\nvorheriger Fälligkeitszeitpunkt war __timeOldValue__", + "act-a-endAt": "hat Ende auf __timeValue__ von (__timeOldValue__) geändert", + "act-a-startAt": "hat Start auf __timeValue__ von (__timeOldValue__) geändert", + "act-a-receivedAt": "hat Empfangszeit auf __timeValue__ von (__timeOldValue__) geändert", + "a-dueAt": "hat Fälligkeit geändert auf", + "a-endAt": "hat Ende geändert auf", + "a-startAt": "hat Startzeit geändert auf", + "a-receivedAt": "hat Empfangszeit geändert auf", + "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", + "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", + "duenow": "aktuelles Fälligkeitsdatum %s heute", + "act-newDue": "__list__/__card__ hat seine 1. fällige Erinnerung [__board__]", + "act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]", + "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", + "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", + "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", + "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.", + "delete-team-confirm-popup": "Sind Sie sicher, daß Sie dieses Team löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", + "delete-org-confirm-popup": "Sind Sie sicher, daß Sie diese Organisation löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", + "accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen", + "hide-minicard-label-text": "Labeltext auf Minikarte ausblenden", + "show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen", + "assignee": "Zugewiesen", + "cardAssigneesPopup-title": "Zugewiesen", + "addmore-detail": "Eine detailliertere Beschreibung hinzufügen", + "show-on-card": "Zeige auf Karte", + "new": "Neu", + "editOrgPopup-title": "Organisation bearbeiten", + "newOrgPopup-title": "Neue Organisation", + "editTeamPopup-title": "Team bearbeiten", + "newTeamPopup-title": "Neues Team", + "editUserPopup-title": "Benutzer ändern", + "newUserPopup-title": "Neuer Benutzer", + "notifications": "Benachrichtigungen", + "view-all": "Alle anzeigen", + "filter-by-unread": "Nur ungelesene", + "mark-all-as-read": "Alle als gelesen markieren", + "remove-all-read": "Alle gelesenen entfernen", + "allow-rename": "Umbenennen erlauben", + "allowRenamePopup-title": "Umbenennen erlauben", + "start-day-of-week": "Wochentagbeginn festlegen", + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag", + "sunday": "Sonntag", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Eigentümer", + "last-modified-at": "Zuletzt geändert um", + "last-activity": "Letzte Aktivität", + "voting": "Abstimunng", + "archived": "Archiviert", + "delete-linked-card-before-this-card": "Sie können diese Karte nicht löschen, bevor verbundene Karten nicht gelöscht wurden.", + "delete-linked-cards-before-this-list": "Sie können diese Liste erst löschen, wenn Sie alle Karten gelöscht haben, die auf Karten in dieser Liste verweisen.", + "hide-checked-items": "Erledigte ausblenden", + "task": "Aufgabe", + "create-task": "Aufgabe erstellen", + "ok": "OK", + "organizations": "Organisationen", + "teams": "Teams", + "displayName": "Anzeigename", + "shortName": "Kurzname", + "website": "Webseite", + "person": "Person", + "my-cards": "Meine Karten", + "card": "Karte", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "Meine Karten Sortierung", + "myCardsSortChangePopup-title": "Meine Karten Sortierung", + "myCardsSortChange-choice-board": "nach Board", + "myCardsSortChange-choice-dueat": "nach Fälligkeitsdatum", + "dueCards-title": "Fällige Karten", + "dueCardsViewChange-title": "Fällige Karten Ansicht", + "dueCardsViewChangePopup-title": "Fällige Karten Ansicht", + "dueCardsViewChange-choice-me": "Ich", + "dueCardsViewChange-choice-all": "alle Benutzer", + "dueCardsViewChange-choice-all-description": "Zeigt alle unvollständigen Karten mit einem *Fälligkeits*-Datum auf Boards, für die der Benutzer Berechtigungen hat.", + "broken-cards": "Fehlerhafte Karten", + "board-title-not-found": "Board „%s“ nicht gefunden.", + "swimlane-title-not-found": "Swimlane „%s“ nicht gefunden.", + "list-title-not-found": "Liste „%s“ nicht gefunden.", + "label-not-found": "Label „%s“ nicht gefunden.", + "label-color-not-found": "Label-Farbe „%s“ nicht gefunden.", + "user-username-not-found": "Nutzername „%s“ nicht gefunden.", + "comment-not-found": "Keine Karte gefunden, die „%s“ in einem Kommentar enthält.", + "globalSearch-title": "Alle Boards durchsuchen", + "no-cards-found": "Keine Karten gefunden", + "one-card-found": "Eine Karte gefunden", + "n-cards-found": "%s Karten gefunden", + "n-n-of-n-cards-found": "__start__–__end__ von __total__ Karten gefunden", + "operator-board": "Board", + "operator-board-abbrev": "b", + "operator-swimlane": "Swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "Liste", + "operator-list-abbrev": "l", + "operator-label": "Label", + "operator-label-abbrev": "#", + "operator-user": "Nutzer", + "operator-user-abbrev": "@", + "operator-member": "Mitglied", + "operator-member-abbrev": "m", + "operator-assignee": "Zugewiesener", + "operator-assignee-abbrev": "a", + "operator-creator": "Ersteller", + "operator-status": "Status", + "operator-due": "Fällig", + "operator-created": "erstellt", + "operator-modified": "geändert", + "operator-sort": "sortieren", + "operator-comment": "Kommentar", + "operator-has": "hat", + "operator-limit": "Begrenzung", + "predicate-archived": "archiviert", + "predicate-open": "offen", + "predicate-ended": "beendet", + "predicate-all": "alle", + "predicate-overdue": "überfällig", + "predicate-week": "Woche", + "predicate-month": "Monat", + "predicate-quarter": "Quartal", + "predicate-year": "Jahr", + "predicate-due": "Fällig", + "predicate-modified": "geändert", + "predicate-created": "erstellt", + "predicate-attachment": "Anhang", + "predicate-description": "Beschreibung", + "predicate-checklist": "Checkliste", + "predicate-start": "Start", + "predicate-end": "Ende", + "predicate-assignee": "Zugewiesener", + "predicate-member": "Mitglied", + "predicate-public": "öffentlich", + "predicate-private": "privat", + "operator-unknown-error": "„%s“ ist kein Operator", + "operator-number-expected": "Operator „__operator__“ erwartete eine Zahl, bekam aber „__value__“", + "operator-sort-invalid": "Sortierung „%s“ ist ungültig", + "operator-status-invalid": "„%s“ ist kein gültiger Status", + "operator-has-invalid": "%s ist keine gültige Prüfung auf Existenz", + "operator-limit-invalid": "%s ist keine gültige Begrenzung. Die Begrenzung sollte eine positive Ganzzahl sein.", + "next-page": "Nächste Seite", + "previous-page": "Vorherige Seite", + "heading-notes": "Bemerkungen", + "globalSearch-instructions-heading": "Hinweise zur Suche", + "globalSearch-instructions-description": "Suchanfragen können Operatoren enthalten, um die Suche zu verfeinern. Operatoren bestehen aus ihrem Namen und ihrem Wert, getrennt durch einen Doppelpunkt. Beispielsweise würde die Operatorangabe `Liste:Blockiert` die Suche beschränken auf Karten in einer Liste namens *Blockiert*. Wenn der Wert Leerschritte oder andere Spezialzeichen enthält, muss er in Anführungszeichen gesetzt sein (z.B. `__operator_list__:\"Im Review\"`).", + "globalSearch-instructions-operators": "Mögliche Operatoren:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` – Karten in Boards, auf die das angegebene *<title>* passt", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` – Karten in Listen, auf die das angegebene *<title>* passt", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` – Karten in Swimlanes, auf die das angebene *<title>* passt", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` – Karten mit einem Kommentar, das *<text>* enthält.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` – Karten, die ein Label haben, auf das *<color>* oder *<name> passt", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<Name|Farbe>` – Kurzform für `__operator_label__:<color>` oder `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` – Karten, für die *<username>* ein *Mitglied* oder ein *Zugewiesener* ist", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` – Kurzform für `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` – Karten, von denen *<username>* *Mitglied* ist", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` – Karten, denen *<username>* *zugewiesen* ist", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` – Karten, die von *<username>* angelegt wurden", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` – Karten, die spätestens in *<n>* Tagen fällig sind. `__operator_due__:__predicate_overdue__` zeigt alle Karten, für die die Fälligkeit überschritten ist.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` – Karten, die vor maximal *<n>* Tagen angelegt wurden", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` – Karten, die vor maximal *<n>* Tagen geändert wurden", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` – wo *<status>* eines der Folgenden ist:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` – archivierte Karten", + "globalSearch-instructions-status-all": "`__predicate_all__` – alle archivierten und unarchivierten Karten", + "globalSearch-instructions-status-ended": "`__predicate_ended__` – Karten mit einem Enddatum", + "globalSearch-instructions-status-public": "`__predicate_public__` – Karten aus öffentlichen Boards", + "globalSearch-instructions-status-private": "`__predicate_private__` – Karten aus privaten Boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` – wo *<field>* eines aus `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` oder `__predicate_member__` ist. Die Angabe eines `-` vor *<field>* sucht nach leerem Feld (z.B. findet `__operator_has__:-fällig` alle Karten ohne Fälligkeitsdatum).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` – wo *<sort-name>* eines aus `__predicate_due__`, `__predicate_created__` oder `__predicate_modified__` ist. Zum absteigenden Sortieren ein `-` vor den Sortierschlüssel setzen.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` – wo *<n>* eine positive Ganzzahl ist, die die Anzahl Karten pro Seite darstellt.", + "globalSearch-instructions-notes-1": "Mehrere Operatoren können angegeben werden.", + "globalSearch-instructions-notes-2": "Gleichartige Operatoren werden Oder-verknüpft. Karten, für die eine Bedingung zutrifft, werden ausgegeben.\n`__operator_list__:Verfügbar __operator_list__:Blockiert` würde alle Karten ausgeben, die in irgendwelchen Listen mit den Namen *Verfügbar* oder *Blockiert* stehen.", + "globalSearch-instructions-notes-3": "Verschiedenartige Operatoren werden *UND*-verknüpft. Nur Karten, auf die alle verschiedenartigen Operatoren zutreffen, werden zurückgegeben. `__operator_list__:Verfügbar __operator_label__:Rot` gibt nur Karten aus der Liste *Verfügbar* mit *rotem* Label zurück.", + "globalSearch-instructions-notes-3-2": "Tage können als positive oder negative Ganzzahl angegeben werden, oder man nutzt `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` oder `__predicate_year__` für den jeweiligen Zeitraum.", + "globalSearch-instructions-notes-4": "Bei Suchen in Texten ist die Groß-/Kleinschreibung egal.", + "globalSearch-instructions-notes-5": "Per Vorgabe werden archivierte Karten bei der Suche nicht berücksichtigt.", + "link-to-search": "Link auf diese Suche", + "excel-font": "Arial", + "number": "Zahl", + "label-colors": "Label-Farben", + "label-names": "Label-Namen", + "archived-at": "archiviert am", + "sort-cards": "Sortiere Karten", + "cardsSortPopup-title": "Sortiere Karten", + "due-date": "Fälligkeitsdatum", + "server-error": "Server-Fehler", + "server-error-troubleshooting": "Bitte übermitteln Sie den Fehler, den der Server erzeugt hat.\nRufen Sie für eine Snap-Installation auf: `sudo snap logs wekan.wekan`\nRufen Sie für eine Docker-Installation auf: `sudo docker logs wekan-app`", + "title-alphabetically": "Überschrift (alphabetisch)", + "created-at-newest-first": "Erstelldatum (neueste zuerst)", + "created-at-oldest-first": "Erstelldatum (älteste zuerst)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Alle System-Nachrichten aller Nutzer verbergen", + "now-system-messages-of-all-users-are-hidden": "Alle System-Nachrichten aller Nutzer sind nun verborgen", + "move-swimlane": "Swimlane verschieben", + "moveSwimlanePopup-title": "Swimlane verschieben", + "custom-field-stringtemplate": "String-Vorlage", + "custom-field-stringtemplate-format": "Format (verwende %{value} als Platzhalter)", + "custom-field-stringtemplate-separator": "Trenner (verwende oder   für einen Leerschritt)", + "custom-field-stringtemplate-item-placeholder": "Drücke die Eingabetaste, um weitere Einträge hinzuzufügen", + "creator": "Ersteller", + "filesReportTitle": "Dateien-Bericht", + "orphanedFilesReportTitle": "Verwaister Datei-Bericht", + "reports": "Berichte", + "rulesReportTitle": "Regeln-Bericht", + "copy-swimlane": "Kopiere Swimlane", + "copySwimlanePopup-title": "Swimlane kopieren", + "display-card-creator": "Karten-Ersteller anzeigen", + "wait-spinner": "Warte-Symbol", + "Bounce": "Puls-Warte-Symbol", + "Cube": "Würfel-Warte-Symbol", + "Cube-Grid": "Würfel-Gitter-Warte-Symbol", + "Dot": "Punkt-Warte-Symbol", + "Double-Bounce": "Doppelpuls-Warte-Symbol", + "Rotateplane": "Drehscheibe-Warte-Symbol", + "Scaleout": "Scaleout-Warte-Symbol", + "Wave": "Wellen-Warte-Symbol", + "maximize-card": "Karte maximieren", + "minimize-card": "Karte minimieren", + "delete-org-warning-message": "Diese Organisation kann nicht gelöscht werden, da wenigstens ein Nutzer dazu gehört.", + "delete-team-warning-message": "Dieses Team kann nicht gelöscht werden, da wenigstens ein Nutzer dazu gehört." +} \ No newline at end of file diff --git a/i18n/el.i18n.json b/i18n/el.i18n.json index 5e4c90649..a61701a9f 100644 --- a/i18n/el.i18n.json +++ b/i18n/el.i18n.json @@ -1,109 +1,116 @@ { "accept": "Αποδοχή", "act-activity-notify": "Ειδοποίηση δραστηριότητας", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addAttachment": "προσετέθη το συνημμένο __attachment__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-deleteAttachment": "διεγράφη το συνημμένο __attachment__ από την κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addSubtask": "προστέθηκε η υποεργασία __subtask__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addLabel": "Προστέθηκε η ετικέτα __label__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addedLabel": "Προστέθηκε η ετικέτα __label__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeLabel": "Διεγράφη η ετικέτα __label__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-removedLabel": "Διεγράφη η ετικέτα __label__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-addChecklist": "προστέθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addChecklistItem": "προστέθηκε το στοιχείο __checklistItem__ στη λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeChecklist": "διεγράφη η λίστα ελέγχου - checklist __checklist__ από την κάρτα __card__ της λίστας __list__ της λωρίδας __swimlane__ του πίνακα __board__", + "act-removeChecklistItem": "διεγράφη το στοιχείο __checklistItem__ από τη λίστα ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__\nεπιλέχθηκε το στοιχείο __checklistItem__ της λίστας ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-uncheckedItem": "αποεπιλέχθηκε το στοιχείο __checklistItem__ της λίστας ελέγχου - checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-completeChecklist": "ολοκληρώθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-uncompleteChecklist": "σημάνθηκε ως ημιτελής η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-addComment": "προσέθεσε σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-editComment": "μετέβαλε σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-deleteComment": "διεγράφη σχόλιο στην κάρτα __card__: __comment__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createBoard": "δημιουργήθηκε ο πίνακας __board__", + "act-createSwimlane": "δημιουργήθηκε η λωρίδα __swimlane__ στον πίνακα __board__", + "act-createCard": "δημιουργήθηκε η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createCustomField": "δημιουργήθηκε το προσαρμοσμένο πεδίο __customField__ στον πίνακα __board__", + "act-deleteCustomField": "διεγράφη το προσαρμοσμένο πεδίο __customField__ στον πίνακα __board__", + "act-setCustomField": "υπέστη επεξεργασία η τιμή του προσαρμοσμένου πεδίου __customField__: __customFieldValue__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-createList": "προστέθηκε η λίστα __list__ στον πίνακα __board__", + "act-addBoardMember": "προστέθηκε το μέλος __member__ στον πίνακα __board__", + "act-archivedBoard": "Ο πίνακας __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedCard": "Η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedList": "Η λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-archivedSwimlane": "Η λωρίδα __swimlane__ στον πίνακα __board__ μεταφέρθηκε στο Αρχείο", + "act-importBoard": "Εισήχθη ο πίνακας __board__", + "act-importCard": "εισήχθη η κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-importList": "εισήχθη η λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-joinMember": "προστέθηκε το μέλος __member__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-moveCard": "μετακίνησε την κάρτα __card__ στον πίνακα __board__ από τη λίστα __oldList__ της λωρίδας __oldSwimlane__ στη λίστα __list__ στη λωρίδα __swimlane__", + "act-moveCardToOtherBoard": "μετακίνησε την κάρτα __card__ από τη λίστα __oldList__ της λωρίδας __oldSwimlane__ του πίνακα __oldBoard__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-removeBoardMember": "αφαιρέθηκε το μέλος __member__ από τον πίνακα __board__", + "act-restoredCard": "επαναφορά της κάρτας __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", + "act-unjoinMember": "διαγραφή του μέλους __member__ από την κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "Ενέργειες", - "activities": "Activities", + "activities": "Δραστηριότητες", "activity": "Δραστηριότητα", - "activity-added": "added %s to %s", + "activity-added": "προστέθηκε %s στο %s", "activity-archived": "%s μετακινήθηκε στο Αρχείο", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", + "activity-attached": "επισυνάφθηκε %s στο %s", + "activity-created": "δημιουργήθηκε %s", + "activity-customfield-created": "δημιούργησε το προσαρμοσμένο πεδίο %s", + "activity-excluded": "εξαιρέθηκε %s από %s", + "activity-imported": "εισήχθη %s στο %s από %s", + "activity-imported-board": "εισήχθη %s από %s", "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", + "activity-moved": "μετακινήθηκε το %s από %s στο %s", + "activity-on": "στό %s", + "activity-removed": "διεγράφη %s από %s", + "activity-sent": "εστάλη %s στο %s", "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", + "activity-subtask-added": "προστέθηκε υποεργασία στο %s", + "activity-checked-item": "επιλέχθηκε %s στη λίστα ελέγχου - checlist %s του %s", "activity-unchecked-item": "unchecked %s in checklist %s of %s", "activity-checklist-added": "added checklist to %s", "activity-checklist-removed": "removed a checklist from %s", "activity-checklist-completed": "completed checklist %s of %s", "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-added": "προστέθηκε ένα στοιχείο λίστας ελέγου στη '%s' στο %s", "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", "add": "Προσθήκη", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checked-item-card": "επιλέχθηκε το %s στη λίστα ελέγχου - checklist %s", + "activity-unchecked-item-card": "αποεπιλέχθηκε το %s στη λίστα ελέγχου - checklist %s", + "activity-checklist-completed-card": "ολοκληρώθηκε η λίστα ελέγχου checklist __checklist__ στην κάρτα __card__ στη λίστα __list__ στη λωρίδα __swimlane__ στον πίνακα __board__", "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", + "activity-editComment": "επεξεργασία σχολίου %s", + "activity-deleteComment": "διεγράφη το σχόλιο %s", + "activity-receivedDate": "η ημερομηνία λήψης άλλαξε σε %s από %s", + "activity-startDate": "η ημερομηνία έναρξης άλλαξε σε %s από %s", + "activity-dueDate": "υπέστη επεξεργασία η τιμή της προθεσμίας σε %s από %s", + "activity-endDate": "η ημερομηνία λήξης άλλαξε σε %s από %s", + "add-attachment": "Προσθήκη Συνημμένου", + "add-board": "Προσθήκη Πίνακα", + "add-template": "Add Template", "add-card": "Προσθήκη Κάρτας", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Προσθήκη Λωρίδας", + "add-subtask": "Προσθήκη Υποεργασίας", + "add-checklist": "Προσθήκη Λίστας ελέγχου", + "add-checklist-item": "Προσθήκη ενός στοιχείου στη λίστα ελέγχου - checklist", "add-cover": "Add Cover", "add-label": "Προσθήκη Ετικέτας", "add-list": "Προσθήκη Λίστας", "add-members": "Προσθήκη Μελών", "added": "Προστέθηκε", - "addMemberPopup-title": "Μέλοι", + "addMemberPopup-title": "Μέλη", "admin": "Διαχειριστής", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", + "admin-desc": "Μπορεί να δει, να επεξεργαστεί κάρτες, να διαγράψει μέλη και να μεταβάλει τις ρυθμίσεις του πίνακα.", + "admin-announcement": "Ανακοίνωση", + "admin-announcement-active": "Ενεργή Ανακοίνωση που είναι ορατή σε όλο το σύστημα", + "admin-announcement-title": "Ανακοίνωση από το Διαχειριστή Συστήματος", + "all-boards": "Όλοι οι πίνακες", + "and-n-other-card": "Και __count__ επιπλέον κάρτα", + "and-n-other-card_plural": "Και __count__ επιπλέον κάρτες", "apply": "Εφαρμογή", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "app-is-offline": "Φορτώνει, παρακαλώ περιμένετε. Η ανανέωση της σελίδας θα προκαλέσει απώλεια δεδομένων. Αν η φόρτωση δεν επιτύχει, παρακαλούμε ελέγξτε ότι ο server δεν έχει σταματήσει.", "archive": "Μετακίνηση στο Αρχείο", "archive-all": "Μετακίνηση Όλων στο Αρχείο", "archive-board": "Μετακίνηση Πίνακα στο Αρχείο", "archive-card": "Μετακίνηση Κάρτας στο Αρχείο", "archive-list": "Μετακίνηση Λίστας στο Αρχείο", - "archive-swimlane": "Move Swimlane to Archive", + "archive-swimlane": "Μετακίνηση της Λωρίδας στο Αρχείο", "archive-selection": "Μετακίνηση επιλογής στο Αρχείο", "archiveBoardPopup-title": "Να μετακινηθεί ο Πίνακας στο Αρχείο;", "archived-items": "Αρχείο", @@ -113,84 +120,123 @@ "archives": "Αρχείο", "template": "Πρότυπο", "templates": "Πρότυπα", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Ανάθεση μέλους", - "attached": "attached", + "attached": "επισυνάφθηκε", "attachment": "Συνημμένο", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Διαγραφή Συννημένου;", - "attachments": "Συννημένα", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "attachment-delete-pop": "Η διαγραφή του συνημμένου είναι μόνιμη. Δεν υπάρχει δυνατότητα επαναφοράς.", + "attachmentDeletePopup-title": "Διαγραφή Συνημμένου;", + "attachments": "Συνημμένα", + "auto-watch": "Αυτόματη παρακολούθηση των πινάκων από τη στιγμή που δημιουργούνται.", + "avatar-too-big": "Το avatar είναι πολύ μεγάλο (μέγιστο 520KB)", "back": "Πίσω", "board-change-color": "Αλλαγή χρώματος", - "board-nb-stars": "%s stars", + "board-nb-stars": "%s αστέρια", "board-not-found": "Ο πίνακας δε βρέθηκε", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", + "board-private-info": "Αυτός ο πίνακας θα είναι <strong>κρυφός</strong>.", + "board-public-info": "Αυτός ο πίνακας θα είναι <strong>δημόσιος</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Αλλαγή Φόντου Πίνακα", "boardChangeTitlePopup-title": "Μετονομασία Πίνακα", "boardChangeVisibilityPopup-title": "Αλλαγή Ορατότητας", "boardChangeWatchPopup-title": "Change Watch", "boardMenuPopup-title": "Ρυθμίσεις Πίνακα", - "boardChangeViewPopup-title": "Board View", + "boardChangeViewPopup-title": "Προβολή Πίνακα", "boards": "Πίνακες", - "board-view": "Board View", + "board-view": "Προβολή Πίνακα", "board-view-cal": "Ημερολόγιο", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", + "board-view-swimlanes": "Λωρίδες", + "board-view-collapse": "Σύμπτυξη", + "board-view-gantt": "Διάγραμμα Gantt", "board-view-lists": "Λίστες", "bucket-example": "Like “Bucket List” for example", "cancel": "Ακύρωση", "card-archived": "Αυτή η κάρτα μετακινήθηκε στο Αρχείο.", "board-archived": "Αυτός ο πίνακας μετακινήθηκε στο Αρχείο.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-comments-title": "Αυτή η κάρτα έχει %s σχόλιο.", + "card-delete-notice": "Η Διαγραφή είναι μόνιμη. Θα χάσετε όλες τις ενέργειες που σχετίζονται με αυτή την κάρτα. ", + "card-delete-pop": "Όλες οι ενέργειες θα αφαιρεθούν από τη ροή δραστηριοτήτων και δε θα μπορείτε να ξανανοίξετε την κάρτα. Δεν υπάρχει δυνατότητα επαναφοράς.", + "card-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια κάρτα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", "card-due": "Έως", "card-due-on": "Έως τις", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", + "card-spent": "Δαπανηθείς Χρόνος", + "card-edit-attachments": "Επεξεργασία συνημμένων", + "card-edit-custom-fields": "Επεξεργασία προσαρμοσμένων πεδίων", + "card-edit-labels": "Επεξεργασία ετικετών", + "card-edit-members": "Επεξεργασία μελών", + "card-labels-title": "Αλλαγή ετικετών για την κάρτα.", + "card-members-title": "Προσθήκη ή διαγραφή μελών του πίνακα από την κάρτα. ", + "card-start": "Έναρξη", + "card-start-on": "Αρχίζει σε", + "cardAttachmentsPopup-title": "Συνημμένο Από", + "cardCustomField-datePopup-title": "Αλλαγή ημερομηνίας", + "cardCustomFieldsPopup-title": "Επεξεργασία προσαρμοσμένων πεδίων", + "cardStartVotingPopup-title": "Έναρξη ψηφοφορίας", + "positiveVoteMembersPopup-title": "Υποστηρικτές", + "negativeVoteMembersPopup-title": "Αντιτιθέμενοι", + "card-edit-voting": "Επεξεργασία ψηφοφορίας", + "editVoteEndDatePopup-title": "Αλλαγή της ημερομηνίας λήξης ψηφοφορίας", + "allowNonBoardMembers": "Επίτρεψε όλους τους συνδεδεμένους χρήστες", + "vote-question": "Ερώτηση ψηφοφορίας", + "vote-public": "Εμφάνισε ποιός ψήφισε τι", + "vote-for-it": "για αυτό", + "vote-against": "εναντίον", + "deleteVotePopup-title": "Διαγραφή ψήφου;", + "vote-delete-pop": "Μόνιμη Διαγραφή. Θα χάσετε όλες τις ενέργειες που σχετίζονται με αυτή την ψήφο.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Διαγραφή Κάρτας;", - "cardDetailsActionsPopup-title": "Card Actions", + "cardDetailsActionsPopup-title": "Ενέργειες κάρτας", "cardLabelsPopup-title": "Ετικέτες", "cardMembersPopup-title": "Μέλοι", "cardMorePopup-title": "Περισσότερα", - "cardTemplatePopup-title": "Create template", + "cardTemplatePopup-title": "Δημιουργία προτύπου", "cards": "Κάρτες", "cards-count": "Κάρτες", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", + "cards-count-one": "Κάρτα", + "casSignIn": "Σύνδεση χρήστη με CAS", + "cardType-card": "Κάρτα", + "cardType-linkedCard": "Συνδεδεμένη Κάρτα", + "cardType-linkedBoard": "Συνδεδεμένος Πίνακας", "change": "Αλλαγή", - "change-avatar": "Change Avatar", + "change-avatar": "Αλλαγή Avatar", "change-password": "Αλλαγή Κωδικού", - "change-permissions": "Change permissions", + "change-permissions": "Αλλαγή δικαιωμάτων", "change-settings": "Αλλαγή Ρυθμίσεων", - "changeAvatarPopup-title": "Change Avatar", + "changeAvatarPopup-title": "Αλλαγή Avatar", "changeLanguagePopup-title": "Αλλαγή Γλώσσας", "changePasswordPopup-title": "Αλλαγή Κωδικού", - "changePermissionsPopup-title": "Change Permissions", + "changePermissionsPopup-title": "Αλλαγή Δικαιωμάτων", "changeSettingsPopup-title": "Αλλαγή Ρυθμίσεων", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "clipboard": "Clipboard or drag & drop", + "subtasks": "Υποεργασίες", + "checklists": "Λίστες Ελέγχου - Checklists", + "click-to-star": "Κλικ για να προσθέσετε αστεράκι στον πίνακα", + "click-to-unstar": "Κλικ για να αφαιρέσετε αστεράκι από τον πίνακα", + "clipboard": "Clipboard ή drag & drop", "close": "Κλείσιμο", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-board": "Κλείσιμο Πίνακα", + "close-board-pop": "Μελλοντικά μπορείτε να επαναφέρετε τον πίνακα κάνοντας κλικ στο κουμπί \"Αρχειοθέτηση\" από την αρχική επικεφαλίδα. ", + "close-card": "Close Card", "color-black": "μαύρο", "color-blue": "μπλε", "color-crimson": "βυσσινί", @@ -200,7 +246,7 @@ "color-green": "πράσινο", "color-indigo": "λουλάκι", "color-lime": "λάιμ", - "color-magenta": "magenta", + "color-magenta": "ματζέντα", "color-mistyrose": "mistyrose", "color-navy": "navy", "color-orange": "πορτοκαλί", @@ -217,216 +263,254 @@ "color-white": "λευκό", "color-yellow": "κίτρινο", "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", + "comment": "Σχόλιο", + "comment-placeholder": "Συγγραφή Σχολίου", + "comment-only": "Μόνο σχόλιο", + "comment-only-desc": "Μπορεί μόνο να σχολιάζει σε κάρτες.", "no-comments": "Χωρίς σχόλια", - "no-comments-desc": "Can not see comments and activities.", + "no-comments-desc": "Δε μπορεί να δει σχόλια και δραστηριότητες.", "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker-desc": "Μπορεί μόνο να μετακινεί κάρτες, να αναθέτει μια κάρτα στον εαυτό του και να σχολιάζει.", "computer": "Υπολογιστής", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", + "confirm-subtask-delete-dialog": "Είστε σίγουροι ότι θέλετε να σβήσετε την υποεργασία;", + "confirm-checklist-delete-dialog": "Είστε σίγουρι ότι θέλετε να διαγράψετε τη λίστα ελέγχου - checklist;", + "copy-card-link-to-clipboard": "Αντιγραφή του συνδέσμου της κάρτας στο clipboard", + "linkCardPopup-title": "Σύνδεση Κάρτας", "searchElementPopup-title": "Αναζήτηση", - "copyCardPopup-title": "Copy Card", - "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyCardPopup-title": "Αντιγραφή Κάρτας", + "copyChecklistToManyCardsPopup-title": "Αντιγραφή του Προτύπου Λίστας Ελέγχου - Checklist σε πολλές Κάρτες", "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Τίτλος πρώτης κάρτας\", \"description\":\"Περιγραφή πρώτης κάρτας\"}, {\"title\":\"Τίτλος δεύτερης κάρτας\",\"description\":\"Περιγραφή δεύτερης κάρτας\"},{\"title\":\"Τίτλος τελευταίας κάρτας\",\"description\":\"Περιγραφή τελευταίας κάρτας\"} ]", "create": "Δημιουργία", - "createBoardPopup-title": "Create Board", + "createBoardPopup-title": "Δημιουργία Πίνακα", "chooseBoardSourcePopup-title": "Εισαγωγή πίνακα", "createLabelPopup-title": "Δημιουργία Ετικέτας", "createCustomField": "Δημιουργία Πεδίου", "createCustomFieldPopup-title": "Δημιουργία Πεδίου", - "current": "current", + "current": "τρέχων", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Νόμισμα", + "custom-field-currency-option": "Κωδικός Νομίσματος", "custom-field-date": "Ημερομηνία", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", + "custom-field-dropdown": "Λίστα επιλογών", + "custom-field-dropdown-none": "(κανένα)", + "custom-field-dropdown-options": "Επιλογές Λίστας", + "custom-field-dropdown-options-placeholder": "Πιέστε enter για να προσθέσετε περισσότερες επιλογές", + "custom-field-dropdown-unknown": "(άγνωστο)", "custom-field-number": "Αριθμός", "custom-field-text": "Κείμενο", - "custom-fields": "Custom Fields", + "custom-fields": "Προσαρμοσμένα Πεδία", "date": "Ημερομηνία", - "decline": "Decline", + "decline": "Απόρριψη", "default-avatar": "Default avatar", "delete": "Διαγραφή", - "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteCustomFieldPopup-title": "Διαγραφή Προσαρμοσμένου Πεδίου;", "deleteLabelPopup-title": "Διαγραφή Ετικέτας;", "description": "Περιγραφή", "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", "discard": "Απόρριψη", - "done": "Done", - "download": "Download", + "done": "Ολοκληρώθηκε", + "download": "Λήψη", "edit": "Επεξεργασία", - "edit-avatar": "Change Avatar", + "edit-avatar": "Αλλαγή Avatar", "edit-profile": "Επεξεργασία Προφίλ", "edit-wip-limit": "Edit WIP Limit", "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", + "editCardStartDatePopup-title": "Αλλαγή ημερομηνίας έναρξης", + "editCardDueDatePopup-title": "Αλλαγή ημερομηνίας λήξης προθεσμίας", "editCustomFieldPopup-title": "Επεξεργασία Πεδίου", - "editCardSpentTimePopup-title": "Change spent time", + "editCardSpentTimePopup-title": "Αλλαγή δαπανηθέντος χρόνου", "editLabelPopup-title": "Αλλαγή Ετικέτας", "editNotificationPopup-title": "Επεξεργασία Ειδοποίησης", "editProfilePopup-title": "Επεξεργασία Προφίλ", "email": "Email", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", + "email-enrollAccount-subject": "Ένας λογαριασμός δημιουργήθηκε για εσάς στο __siteName__", + "email-enrollAccount-text": "Χαίρετε __user__,\n\nΓια να ξεκινήσετε να χρησιμοποιείτε αυτή την υπηρεσία, απλώς κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", + "email-fail": "Η αποστολή του email απέτυχε", + "email-fail-text": "Σφάλμα κατά την αποστολή του email", + "email-invalid": "Μη έγκυρο email", "email-invite": "Πρόσκληση μέσω Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", + "email-invite-subject": "__inviter__ σας έστειλε μια πρόσκληση", + "email-invite-text": "Αγαπητέ/ή __user__,\n\n__inviter__ σας προσκαλεί να λάβετε μέρος στον πίνακα \"__board__\" για να συνεργαστείτε.\n\nΠαρακαλούμε επιλέξτε τον παρακάτω σύνδεσμο:\n\n__url__\n\nΕυχαριστούμε.", + "email-resetPassword-subject": "Επαναφορά του κωδικού σας για το __siteName__", + "email-resetPassword-text": "Χαίρετε __user__,\n\nΓια να αλλάξετε τον κωδικό πρόσβασής σας, κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", + "email-sent": "Εστάλη Email", "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "email-verifyEmail-text": "Χαίρετε __user__,\n\nΓια να επιβεβαιώσετε το email που χρησιμοποιεί ο λογαριασμός σας, απλώς κάνετε κλικ στον παρακάτω σύνδεσμο.\n\n__url__\n\nΕυχαριστούμε.", "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", + "error-board-doesNotExist": "Αυτός ο πίνακας δεν υφίσταται", + "error-board-notAdmin": "Πρέπει να είστε διαχειριστής του πίνακα αυτού για να προβείτε σε αυτό", + "error-board-notAMember": "Πρέπει να είστε μέλος του πίνακα αυτού για να προβείτε σε αυτό", "error-json-malformed": "Το κείμενο δεν είναι έγκυρο JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-json-schema": "Τα JSON δεδομένα σας δεν περιλαμβάνουν την ορθή πληροφορία στην ορθή μορφοποίηση ", + "error-csv-schema": "Το CSV(Comma Separated Values)/TSV (Tab Separated Values) σας δεν περιλαμβάνει την ορθή πληροφορία σε ορθή μορφοποίηση ", "error-list-doesNotExist": "Η λίστα δεν υπάρχει", "error-user-doesNotExist": "Ο χρήστης δεν υπάρχει", - "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notAllowSelf": "Δε μπορείτε να αυτοπροσκληθείτε", "error-user-notCreated": "Ο χρήστης δε δημιουργήθηκε", - "error-username-taken": "This username is already taken", - "error-email-taken": "Email has already been taken", + "error-username-taken": "Το όνομα χρήστη είναι ήδη κατειλημμένο", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Το email είναι ήδη κατειλημμένο", "export-board": "Εξαγωγή πίνακα", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "export-board-json": "Εξαγωγή πίνακα σε JSON", + "export-board-csv": "Εξαγωγή πίνακα σε CSV", + "export-board-tsv": "Εξαγωγή πίνακα σε TSV", + "export-board-excel": "Εξαγωγή πίνακα σε Excel", + "user-can-not-export-excel": "Ο χρήστης δε μπορεί να εξάγει σε Excel", + "export-board-html": "Εξαγωγή πίνακα σε HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Εξαγωγή πίνακα", + "exportCardPopup-title": "Export card", + "sort": "Ταξινόμηση", + "sort-desc": "Κάντε κλικ για να ταξινομήστε τη λίστα", + "list-sort-by": "Ταξινόμηση λίστας βάσει:", + "list-label-modifiedAt": "Τελευταία Προσπέλαση", + "list-label-title": "Ονομασία Λίστας", + "list-label-sort": "Η καθορισμένη σας Ταξινόμηση", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Φίλτρο", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-no-label": "No label", + "filter-cards": "Φιλτράρετε Κάρτες ή Λίστες", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Φιλτράρετε Λίστα βάσει Τίτλου", + "filter-clear": "Καθαρισμός φίλτρου", + "filter-labels-label": "Φίλτρο βάσει ετικέτας", + "filter-no-label": "Καμμία ετικέτα", + "filter-member-label": "Φίλτρο βάσει μέλους", "filter-no-member": "Κανένα μέλος", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-assignee-label": "Φίλτρο βάσει ανάθεσης μέλους", + "filter-no-assignee": "Κανένας ανατεθείς", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "Κανένα Προσαρμοσμένο Πεδίο", + "filter-show-archive": "Προβολή αρχειοθετημένων λιστών", + "filter-hide-empty": "Απόκρυψη άδειων λιστών", + "filter-on": "Φίλτρο Ενεργό", + "filter-on-desc": "Έχετε ενεργοποιημένο το φίλτρο καρτών σε αυτόν τον πίνακα. Κάνετε κλικ εδώ για να αλλάξετε το φίλτρο.", "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", + "other-filters-label": "Άλλα φίλτρα", + "advanced-filter-label": "Φιλτράρισμα για Προχωρημένους", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Πλήρες Όνομα", - "header-logo-title": "Go back to your boards page.", - "hide-system-messages": "Hide system messages", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", + "header-logo-title": "Επιστροφή στη σελίδα των πινάκων σας.", + "hide-system-messages": "Απόκρυψη μηνυμάτων συστήματος", + "headerBarCreateBoardPopup-title": "Δημιουργία Πίνακα", + "home": "Αρχική", "import": "Εισαγωγή", - "link": "Link", - "import-board": "import board", + "impersonate-user": "Impersonate user", + "link": "Σύνδεσμος", + "import-board": "Εισαγωγή πίνακα", "import-board-c": "Εισαγωγή πίνακα", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-trello": "Εισαγωγή πίνακα από το Trello", + "import-board-title-wekan": "Εισαγωγή πίνακα από προηγούμενη εξαγωγή", + "import-board-title-csv": "Εισαγωγή πίνακα από CSV/TSV", "from-trello": "Από το Trello", - "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "from-wekan": "Aπό προηγούμενη εξαγωγή", + "from-csv": "Από CSV/TSV", + "import-board-instruction-trello": "Στον πίνακα του Trello, πλοηγηθείτε στο 'Menu', έπειτα 'More', 'Print and Export', 'Export JSON' και αντιγράψτε το παραχθέν κείμενο.", + "import-board-instruction-csv": "Επικολλήστε τις Τιμές Διαχωρισμένες με Κόμμα (CSV)/ Τιμές Διαχωρισμένες με Tab (TSV) .", + "import-board-instruction-wekan": "Στον πίνακά σας, πλοηγηθείτε στο 'Menu', έπειτα 'Export board' και αντιγράψετε το κείμενο στο ληφθένα αρχείο.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", + "import-json-placeholder": "Επικολλήστε τα ορθά JSON δεδομένα σας εδώ", + "import-csv-placeholder": "Επικολλήστε τα ορθά CSV/TSV δεδομένα σας εδώ", + "import-map-members": "Αντιστοίχιση μελών", + "import-members-map": "Ο εισαχθείς πίνακας έχει κάποια μέλη. Παρακαλούμε αντιστοιχίστε τα μέλη που θέλετε να εισάγετε στους χρήστες σας", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Αναθεώρηση αντιστοίχισης μελών", + "import-user-select": "Επιλέξτε τον ήδη υπάρχοντα χρήστη που επιθυμείτε να χρησιμοποιήσετε ως αυτό το μέλος", "importMapMembersAddPopup-title": "Επιλογή μέλους", "info": "Έκδοση", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", + "initials": "Αρχικά", + "invalid-date": "Λανθασμένη ημερομηνία", + "invalid-time": "Λανθασμένη ώρα", + "invalid-user": "Λανθασμένος χρήστης", "joined": "joined", - "just-invited": "You are just invited to this board", + "just-invited": "Μόλις προσκληθήκατε σε αυτόν τον πίνακα", "keyboard-shortcuts": "Συντομεύσεις πληκτρολογίου", "label-create": "Δημιουργία Ετικέτας", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "label-default": "%s ετικέτα (προεπιλογή)", + "label-delete-pop": "Δεν υπάρχει δυνατότητα επαναφοράς. Θα διαγραφεί η ετικέτα από όλες τις κάρτες και θα καταστραφεί το ιστορικό της.", "labels": "Ετικέτες", "language": "Γλώσσα", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", + "last-admin-desc": "Δε μπορείτε να μεταβάλετε ρόλους επειδή πρέπει να υπάρχει τουλάχιστον ένας διαχειριστής.", + "leave-board": "Αποχώρηση από Πίνακα", + "leave-board-pop": "Είστε σίγουροι ότι θέλετε να αποχωρήσετε από το __boardTitle__? Θα αφαιρεθείτε από όλες τις κάρτες αυτού του πίνακα.", + "leaveBoardPopup-title": "Αποχωρείτε απο τον Πίνακα;", + "link-card": "Σύνδεσμος σε αυτή την κάρτα", + "list-archive-cards": "Μεταφορά όλων των καρτών αυτής της λίστας στο Αρχείο", "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", + "list-move-cards": "Μεταφορά όλων των καρτών αυτής της λίστας", + "list-select-cards": "Επιλογή όλων των καρτών αυτής της λίστας", + "set-color-list": "Ρύθμιση Χρώματος", + "listActionPopup-title": "Ενέργειες Λίστας", + "settingsUserPopup-title": "Ρυθμίσεις Χρήστη", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Ενέργειες λωρίδας", + "swimlaneAddPopup-title": "Προσθήκη μιας Λωρίδας παρακάτω", "listImportCardPopup-title": "Εισαγωγή μιας κάρτας Trello", + "listImportCardsTsvPopup-title": "Εισαγωγή Excel CSV/TSV", "listMorePopup-title": "Περισσότερα", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "link-list": "Σύνδεσμος σε αυτή τη λίστα", + "list-delete-pop": "Όλες οι ενέργειες θα αφαιρεθούν από τη ροή δραστηριοτήτων και δε θα μπορείτε να ανακτήσετε τη λίστα. Δεν υπάρχει δυνατότητα επαναφοράς.", + "list-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια λίστα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", "lists": "Λίστες", - "swimlanes": "Swimlanes", + "swimlanes": "Λωρίδες", "log-out": "Αποσύνδεση", "log-in": "Σύνδεση", "loginPopup-title": "Σύνδεση", - "memberMenuPopup-title": "Member Settings", + "memberMenuPopup-title": "Ρυθμίσεις Μελών", "members": "Μέλοι", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "multi-selection": "Multi-Selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", + "menu": "Μενού", + "move-selection": "Μετακίνηση επιλογής", + "moveCardPopup-title": "Μετακίνηση Κάρτας", + "moveCardToBottom-title": "Μετακίνηση στην Αρχή", + "moveCardToTop-title": "Μετακίνηση στο Τέλος", + "moveSelectionPopup-title": "Μετακίνηση επιλογής", + "multi-selection": "Πολλαπλή Επιλογή", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Πολλαπλή Επιλογή ενεργοποιημένη", + "muted": "Σίγαση", + "muted-info": "Δεν πρόκειται να ενημερωθείτε ποτέ για οποιεσδήποτε αλλαγές σε αυτόν τον πίνακα", + "my-boards": "Οι Πίνακες μου", "name": "Όνομα", "no-archived-cards": "Δεν υπάρχουν κάρτες στο Αρχείο.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", + "no-archived-lists": "Δεν υπάρχουν λίστες στο Αρχείο.", + "no-archived-swimlanes": "Δεν υπάρχουν λωρίδες στο Αρχείο.", "no-results": "Κανένα αποτέλεσμα", - "normal": "Normal", + "normal": "Κανονικό", "normal-desc": "Can view and edit cards. Can't change settings.", - "not-accepted-yet": "Invitation not accepted yet", + "not-accepted-yet": "Η πρόσκληση δεν έχει λάβει αποδοχή ακόμη", "notify-participate": "Receive updates to any cards you participate as creater or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", + "optional": "προεραιτικό", "or": "ή", "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", "page-not-found": "Η σελίδα δεν βρέθηκε.", "password": "Κωδικός", "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", + "participating": "Συμμετέχει", "preview": "Προεπισκόπηση", "previewAttachedImagePopup-title": "Προεπισκόπηση", "previewClipboardImagePopup-title": "Προεπισκόπηση", "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", + "private-desc": "Ο πίνακας αυτός είναι προσωπικός. Μόνο άτομα που έχουν προστεθεί σε αυτόν τον πίνακα μπορούν να τον δουν και να τον μεταβάλλουν.", "profile": "Προφίλ", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "public": "Δημόσιο", + "public-desc": "Αυτός ο πίνακας είναι δημόσιος. Είναι ορατός σε οποιονδήποτε κατέχει το σύνδεσμο προς αυτόν και θα εμφανίζεται σε μηχανές αναζήτησης όπως η Google. Ο πίνακας μπορεί να μεταβληθεί μόνο από άτομα που έχουν προστεθεί σε αυτόν.", "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove Cover", + "remove-cover": "Αφαίρεση Σκεπάσματος", "remove-from-board": "Αφαίρεση από Πίνακα", "remove-label": "Αφαίρεση Ετικέτας", "listDeletePopup-title": "Διαγραφή Λίστας;", @@ -436,57 +520,67 @@ "removeMemberPopup-title": "Αφαίρεση Μέλους;", "rename": "Μετανομασία", "rename-board": "Μετονομασία Πίνακα", - "restore": "Restore", + "restore": "Επαναφορά", "save": "Αποθήκευση", "search": "Αναζήτηση", - "rules": "Rules", + "rules": "Κανόνες", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Επιλέξτε Χρώμα", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "select-board": "Επιλογή Πίνακα", + "set-wip-limit-value": "Προσδιορισμός ορίου στο μέγιστο αριθμό εργασιών σε αυτή τη λίστα.", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", "shortcut-autocomplete-emoji": "Autocomplete emoji", "shortcut-autocomplete-members": "Autocomplete members", "shortcut-clear-filters": "Καθαρισμός φίλτρων", - "shortcut-close-dialog": "Close Dialog", + "shortcut-close-dialog": "Κλείσιμο Διαλόγου", "shortcut-filter-my-cards": "Φιλτράρισμα των καρτών μου", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", + "sidebar-open": "Άνοιγμα Πλευρικού Μενού", + "sidebar-close": "Κλείσιμο Πλευρικού Μενού", "signupPopup-title": "Δημιουργία Λογαριασμού", "star-board-title": "Click to star this board. It will show up at top of your boards list.", "starred-boards": "Starred Boards", "starred-boards-description": "Starred boards show up at the top of your boards list.", "subscribe": "Εγγραφή", "team": "Ομάδα", - "this-board": "this board", + "this-board": "αυτόν τον πίνακα", "this-card": "αυτή η κάρτα", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", + "spent-time-hours": "Δαπανηθείς Χρόνος (ώρες)", + "overtime-hours": "Υπερωρία (ώρες)", + "overtime": "Υπερωρία", + "has-overtime-cards": "Έχει κάρτες με υπερωρία", + "has-spenttime-cards": "Έχει κάρτες με δαπανηθέντα χρόνο", "time": "Ώρα", "title": "Τίτλος", - "tracking": "Tracking", + "tracking": "Καταγραφή", "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", "type": "Τύπος", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", + "unassign-member": "Απο-ανάθεση μέλους", + "unsaved-description": "Έχετε μια μη αποθηκευμένη περιγραφή.", "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", + "upload": "Μεταφόρτωση", + "upload-avatar": "Μεταφόρτωση ενός avatar", + "uploaded-avatar": "Μεταφόρτωσε ένα avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Όνομα Χρήστη", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", + "watch": "Παρακολούθηση", + "watching": "Παρακολουθούν", + "watching-info": "Θα ενημερωθείτε για οποιαδήποτε αλλαγή σε αυτόν τον πίνακα", "welcome-board": "Πίνακας Καλωσορίσματος", "welcome-swimlane": "Milestone 1", "welcome-list1": "Basics", @@ -501,44 +595,44 @@ "admin-panel": "Admin Panel", "settings": "Ρυθμίσεις", "people": "Άνθρωποι", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", + "registration": "Εγγραφή", + "disable-self-registration": "Απενεργοποίηση Αυτό-Εγγραφής", "invite": "Πρόσκληση", - "invite-people": "Invite People", - "to-boards": "To board(s)", + "invite-people": "Πρόσκάλεσε Ανθρώπους", + "to-boards": "Στον πίνακα(ες)", "email-addresses": "Email Διευθύνσεις", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", + "smtp-host-description": "Η διεύθυνση του SMTP server που διαχειρίζεται τα emails σας.", + "smtp-port-description": "Η πόρτα που ο SMTP server χρησιμοποιεί για τα εξερχόμενα emails.", + "smtp-tls-description": "Ενεργοποίηση υποστήριξης TLS για το SMTP server", + "smtp-host": "Διακομιστής SMTP (host)", + "smtp-port": "Πόρτα SMTP (Port)", "smtp-username": "Όνομα Χρήστη", "smtp-password": "Κωδικός", "smtp-tls": "TLS υποστήριξη", "send-from": "Από", - "send-smtp-test": "Send a test email to yourself", + "send-smtp-test": "Στείλε ένα δοκιμαστικό email στον εαυτό σου", "invitation-code": "Κωδικός Πρόσκλησης", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", + "email-invite-register-subject": "__inviter__ σας έστειλε μια πρόσκληση", + "email-invite-register-text": "Αγαπητέ __user__,\n\n__inviter__ σας προσκαλεί για να συμμετάσχετε στον πίνακα kanban.\n\nΠαρακαλούμε πιέστε την παρακάτω διεύθυνση:\n__url__\n\nΟ κωδικός πρόσκλησής σας είναι: __icode__\n\nΕυχαριστούμε.", + "email-smtp-test-subject": "SMTP Δοκιμαστικό Email", + "email-smtp-test-text": "Στείλατε επιτυχώς ένα email", "error-invitation-code-not-exist": "Ο κωδικός πρόσκλησης δεν υπάρχει", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "error-notAuthorized": "Δεν έχετε εξουσιοδότηση για να δείτε αυτή τη σελίδα.", + "webhook-title": "Όνομα Webhook", + "webhook-token": "Token (Προεραιτικό για Αυθεντικοποίηση)", + "outgoing-webhooks": "Εξερχόμενα Webhooks", + "bidirectional-webhooks": "Αμφίδρομα Webhooks", + "outgoingWebhooksPopup-title": "Εξερχόμενα Webhooks", "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", + "disable-webhook": "Απενεργοποίηση αυτού του Webhook", "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", + "new-outgoing-webhook": "Νέο Εξερχόμενο Webhook", "no-name": "(Άγνωστο)", "Node_version": "Έκδοση Node", "Meteor_version": "Έκδοση Meteor", "MongoDB_version": "Έκδοση MongoDB", "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "MongoDB_Oplog_enabled": "MongoDB Oplog ενεργοποιημένο", "OS_Arch": "OS Arch", "OS_Cpus": "OS CPU Count", "OS_Freemem": "OS Free Memory", @@ -548,44 +642,47 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", - "days": "days", + "days": "ημέρες", "hours": "ώρες", "minutes": "λεπτά", "seconds": "δευτερόλεπτα", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "show-field-on-card": "Προβολή αυτού του πεδίου στην κάρτα", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Ναι", "no": "Όχι", "accounts": "Λογαριασμοί", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", + "accounts-allowEmailChange": "Επίτρεψε Αλλαγή Email", + "accounts-allowUserNameChange": "Επίτρεψε Αλλαγή Ονόματος χρήστη", + "createdAt": "Δημιουργήθηκε στις", + "modifiedAt": "Modified at", + "verified": "Επιβεβαιώθηκε", "active": "Ενεργό", - "card-received": "Received", - "card-received-on": "Received on", + "card-received": "Ελήφθη", + "card-received-on": "Ελήφθη στις", "card-end": "Τέλος", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", + "card-end-on": "Λήγει στις", + "editCardReceivedDatePopup-title": "Αλλαγή ημερομηνίας λήψης", + "editCardEndDatePopup-title": "Αλλαγή ημερομηνίας λήξης", + "setCardColorPopup-title": "Ρύθμιση χρώματος", + "setCardActionsColorPopup-title": "Επιλέξτε ένα χρώμα", + "setSwimlaneColorPopup-title": "Επιλέξτε ένα χρώμα", + "setListColorPopup-title": "Επιλέξτε ένα χρώμα", + "assigned-by": "Ανατέθηκε Από", + "requested-by": "Αιτήθηκε Από", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Διαγραφή Πίνακα;", "delete-board": "Διαγραφή Πίνακα", "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", + "default": "Προεπιλογή", + "queue": "Ουρά αναμονής", + "subtask-settings": "Ρυθμίσεις υποεργασιών (subtasks)", + "card-settings": "Ρυθμίσεις Κάρτας", "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", + "boardCardSettingsPopup-title": "Ρυθμίσεις Κάρτας", "deposit-subtasks-board": "Deposit subtasks to this board:", "deposit-subtasks-list": "Landing list for subtasks deposited here:", "show-parent-in-minicard": "Show parent in minicard:", @@ -594,46 +691,49 @@ "subtext-with-full-path": "Subtext with full path", "subtext-with-parent": "Subtext with parent", "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", + "parent-card": "Μητρική κάρτα", + "source-board": "Κάρτα προέλευσης", "no-parent": "Don't show parent", "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-removed-label": "διεγράφη ετικέτα '%s' από %s", + "activity-delete-attach": "διεγράφη ένα συνημμένο από %s", + "activity-added-label-card": "προσετέθη ετικέτα '%s'", + "activity-removed-label-card": "διεγράφη ετικέτα '%s'", + "activity-delete-attach-card": "διεγραφη ένα συνημμένο", + "activity-set-customfield": "ανάθεση τιμής του custom πεδίου '%s' στο '%s' στο %s", "activity-unset-customfield": "unset custom field '%s' in %s", "r-rule": "Κανόνας", "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", + "r-add-action": "Προσθήκη ενέργειας", + "r-board-rules": "Κανόνες πίνακα", "r-add-rule": "Προσθήκη κανόνα", "r-view-rule": "Προβολή κανόνα", "r-delete-rule": "Διαγραφή κανόνα", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", + "r-new-rule-name": "Νέος τίτλος κανόνα", + "r-no-rules": "Κανένας κανόνας", + "r-trigger": "Trigger", + "r-action": "Ενέργεια", "r-when-a-card": "Όταν μία κάρτα", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", + "r-is": "είναι", + "r-is-moved": "έχει μετακινηθεί", + "r-added-to": "Προσετέθη στο", + "r-removed-from": "Διεγράφη από", + "r-the-board": "ο πίνακας", + "r-list": "λίστα", + "list": "List", + "set-filter": "Καθορισμός Φίλτρου", + "r-moved-to": "Μετακινήθηκε σε", + "r-moved-from": "Μετακινήθηκε από", "r-archived": "Μετακινήθηκε στο Αρχείο", "r-unarchived": "Επαναφέρθηκε από το Αρχείο", "r-a-card": "μία κάρτα", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", + "r-when-a-label-is": "Όταν μια ετικέτα είναι", + "r-when-the-label": "Όταν η ετικέτα", + "r-list-name": "όνομα λίστα", + "r-when-a-member": "Όταν ένα μέλος είναι", + "r-when-the-member": "Όταν το μέλος", + "r-name": "όνομα", + "r-when-a-attach": "Όταν ένα συνημμένο", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", "r-completed": "Completed", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Μετακίνηση κάρτας στην αρχή της λίστας της", @@ -687,20 +788,20 @@ "r-d-remove-all-member": "Remove all member", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", + "r-d-check-one": "Επιλογή στοιχείου", + "r-d-uncheck-one": "Αποεπιλογή στοιχείου", "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", + "r-d-add-checklist": "Προσθήκη λίστας ελέγχου", + "r-d-remove-checklist": "Διαγραφή λίστας ελέγχου", "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", + "r-add-checklist": "Προσθήκη λίστας ελέγχου", + "r-with-items": "με στοιχεία", + "r-items-list": "στοιχείο1,στοιχείο2,στοιχείο3", + "r-add-swimlane": "Προσθήκη λωρίδας", + "r-swimlane-name": "ονομασία λωρίδας", "r-board-note": "Note: leave a field empty to match every possible value.", "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", + "r-when-a-card-is-moved": "Όταν η κάρτα μετακινηθεί σε μια άλλη λίστα", "r-set": "Set", "r-update": "Update", "r-datefield": "date field", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Κάρτα", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "λίστα", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Αριθμός", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json index fe50565c0..bf5f8f711 100644 --- a/i18n/en-GB.i18n.json +++ b/i18n/en-GB.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change colour", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Colour", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in a list in the Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index bc07e9341..c1ae6f911 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,6 +711,8 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", @@ -621,6 +720,7 @@ "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "list", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -633,7 +733,6 @@ "r-when-a-member": "When a member is", "r-when-the-member": "When the member", "r-name": "name", - "r-is": "is", "r-when-a-attach": "When an attachment", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", @@ -647,7 +746,6 @@ "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-list": "list", "r-archive": "Move to Archive", "r-unarchive": "Restore from Archive", "r-card": "card", @@ -667,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -702,7 +801,6 @@ "r-swimlane-name": "swimlane name", "r-board-note": "Note: leave a field empty to match every possible value. ", "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-added-to": "added to", "r-when-a-card-is-moved": "When a card is moved to another list", "r-set": "Set", "r-update": "Update", @@ -728,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is: ", + "team-number": "The number of teams is: ", "people-number": "The number of people is: ", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -753,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -761,12 +863,201 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "list": "List", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } diff --git a/i18n/eo.i18n.json b/i18n/eo.i18n.json index e1d8dde64..1ea858560 100644 --- a/i18n/eo.i18n.json +++ b/i18n/eo.i18n.json @@ -1,15 +1,15 @@ { "accept": "Akcepti", "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addAttachment": "aldonita alligitecon __attachment__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-deleteAttachment": "forigita alligitecon __attachment__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-addSubtask": "aldonita subtaskon __subtask__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-addLabel": "Aldonita etikedo __label__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane en la tabulo __board__", + "act-addedLabel": "Aldonita etikedo __label__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane en la tabulo __board__", + "act-removeLabel": "Forigita etikedo __label__ de karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-removedLabel": "Forigita etikedo __label__ de karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-addChecklist": "aldonita kontrololiston __checklist__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", + "act-addChecklistItem": "aldonita kontrolliston __checklistItem__ al kontrololisto __checklist__ en la karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__", "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -93,7 +100,7 @@ "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All boards", + "all-boards": "Ĉiuj tabuloj", "and-n-other-card": "And __count__ other card", "and-n-other-card_plural": "And __count__ other cards", "apply": "Apliki", @@ -113,6 +120,8 @@ "archives": "Arkivi", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Reen", "board-change-color": "Ŝanĝi koloron", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Renomi tavolon", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Listoj", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Etikedoj", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Kartoj", "cards-count": "Kartoj", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Fermi", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "nigra", "color-blue": "blua", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Dato", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Via teksto estas nevalida JSON", "error-json-schema": "Via JSON ne enhavas la ĝustajn informojn en ĝusta formato", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Tio listo ne ekzistas", "error-user-doesNotExist": "Tio uzanto ne ekzistas", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "Uzanto ne kreita", "error-username-taken": "Uzantnomo jam prenita", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "Nenia etikedo", + "filter-member-label": "Filter by member", "filter-no-member": "Nenia membro", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Plena nomo", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Krei tavolon", "home": "Hejmo", "import": "Importi", + "impersonate-user": "Impersonate user", "link": "Ligilo", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Elektu ĉiujn kartojn en tiu listo.", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Pli", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Movi supren", "moveSelectionPopup-title": "Movi elekton", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Serĉi", "rules": "Reguloj", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -461,7 +547,7 @@ "star-board-title": "Click to star this board. It will show up at top of your boards list.", "starred-boards": "Starred Boards", "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", + "subscribe": "Aboni", "team": "Teamo", "this-board": "this board", "this-card": "this card", @@ -481,7 +567,15 @@ "upload": "Alŝuti", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Uzantnomo", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Rigardi", @@ -499,7 +593,7 @@ "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", "admin-panel": "Admin Panel", - "settings": "Settings", + "settings": "Agordoj", "people": "People", "registration": "Registration", "disable-self-registration": "Disable Self-Registration", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Jes", "no": "Ne", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Forigi regulon", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "listo", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "temo", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Posedanto", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "listo", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "membro", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "kontrololisto", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "membro", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Nombro", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/es-AR.i18n.json b/i18n/es-AR.i18n.json index 3450858fa..5482a9c41 100644 --- a/i18n/es-AR.i18n.json +++ b/i18n/es-AR.i18n.json @@ -1,20 +1,20 @@ { "accept": "Aceptar", "act-activity-notify": "Notificación de Actividad", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addAttachment": "agregado archivo adjunto __attachment__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-deleteAttachment": "eliminado archivo adjunto __attachment__ de la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-addSubtask": "agregada subtarea __subtask__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-addLabel": "Agregada etiqueta __label__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-addedLabel": "Agregada etiqueta __label__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-removeLabel": "Eliminada etiqueta __label__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-removedLabel": "Eliminada etiqueta __label__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-addChecklist": "agregada checklist __checklist__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-addChecklistItem": "agregado ítem __checklistItem__ a checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-removeChecklist": "eliminada checklist __checklist__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-removeChecklistItem": "eliminado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-checkedItem": "marcado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-uncheckedItem": "desmarcado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", + "act-completeChecklist": "completada checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -71,13 +71,20 @@ "add": "Agregar", "activity-checked-item-card": "checked %s in checklist %s", "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-completed-card": "completada checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__", "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-editComment": "comentario %s editado", + "activity-deleteComment": "comentario %s eliminado", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Agregar Adjunto", "add-board": "Agregar Tablero", + "add-template": "Add Template", "add-card": "Agregar Tarjeta", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Agregar Calle", "add-subtask": "Agregar Subtarea", "add-checklist": "Agregar Lista de Tareas", @@ -113,6 +120,8 @@ "archives": "Archivar", "template": "Plantilla", "templates": "Plantillas", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Asignar miembro", "attached": "adjunto(s)", "attachment": "Adjunto", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "¿Borrar Adjunto?", "attachments": "Adjuntos", "auto-watch": "Seguir tableros automáticamente al crearlos", - "avatar-too-big": "El avatar es muy grande (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Atrás", "board-change-color": "Cambiar color", "board-nb-stars": "%s estrellas", "board-not-found": "Tablero no encontrado", "board-private-info": "Este tablero va a ser <strong>privado</strong>.", "board-public-info": "Este tablero va a ser <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Cambiar Fondo del Tablero", "boardChangeTitlePopup-title": "Renombrar Tablero", "boardChangeVisibilityPopup-title": "Cambiar Visibilidad", @@ -138,6 +148,7 @@ "board-view-cal": "Calendario", "board-view-swimlanes": "Calles", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Listas", "bucket-example": "Como \"Lista de Contenedores\" por ejemplo", "cancel": "Cancelar", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Adjuntar De", "cardCustomField-datePopup-title": "Cambiar fecha", "cardCustomFieldsPopup-title": "Editar campos personalizados", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "¿Borrar Tarjeta?", "cardDetailsActionsPopup-title": "Acciones de la Tarjeta", "cardLabelsPopup-title": "Etiquetas", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Crear plantilla", "cards": "Tarjetas", "cards-count": "Tarjetas", + "cards-count-one": "Tarjeta", "casSignIn": "Ingresar con CAS", "cardType-card": "Tarjeta", "cardType-linkedCard": "Tarjeta Vinculada", @@ -191,6 +236,7 @@ "close": "Cerrar", "close-board": "Cerrar Tablero", "close-board-pop": "Podrás restaurar el tablero clickeando el \"Archivo\" desde el encabesado de inicio.", + "close-card": "Close Card", "color-black": "negro", "color-blue": "azul", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "actual", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Fecha", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(ninguno)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Necesitás ser miembro de este tablero para hacer eso", "error-json-malformed": "Tu texto no es JSON válido", "error-json-schema": "Tus datos JSON no incluyen la información correcta en el formato adecuado", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Esta lista no existe", "error-user-doesNotExist": "Este usuario no existe", "error-user-notAllowSelf": "No podés invitarte a vos mismo", "error-user-notCreated": " El usuario no se creó", "error-username-taken": "El nombre de usuario ya existe", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "El email ya existe", "export-board": "Exportar tablero", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar tablero", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtrar", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Sacar filtro", + "filter-labels-label": "Filter by label", "filter-no-label": "Sin etiqueta", + "filter-member-label": "Filter by member", "filter-no-member": "No es miembro", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "El filtro está activado", "filter-on-desc": "Estás filtrando cartas en este tablero. Clickeá acá para editar el filtro.", "filter-to-selection": "Filtrar en la selección", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nombre Completo", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Crear Tablero", "home": "Inicio", "import": "Importar", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "importar tablero", "import-board-c": "Importar tablero", "import-board-title-trello": "Importar tablero de Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "El tablero importado va a borrar todos los datos existentes en el tablero y reemplazarlos con los del tablero en cuestión.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "De Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego a 'Más', 'Imprimir y Exportar', 'Exportar JSON', y copia el texto resultante.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Pegá tus datos JSON válidos acá", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Mapear Miembros", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Revisar mapeo de miembros", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Selecciona todas las tarjetas en esta lista", "set-color-list": "Set Color", "listActionPopup-title": "Listar Acciones", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Acciones de la Calle", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Importar una tarjeta Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Mas", "link-list": "Enlace a esta lista", "list-delete-pop": "Todas las acciones van a ser eliminadas del agregador de actividad y no podás recuperar la lista. No se puede deshacer.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Mover al Tope", "moveSelectionPopup-title": "Mover selección", "multi-selection": "Multi-Selección", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-selección está activo", "muted": "Silenciado", "muted-info": "No serás notificado de ningún cambio en este tablero", @@ -441,8 +525,9 @@ "search": "Buscar", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "¿Texto a buscar?", + "search-example": "Write text you search and press Enter", "select-color": "Seleccionar Color", + "select-board": "Select Board", "set-wip-limit-value": "Fijar un límite para el número máximo de tareas en esta lista", "setWipLimitPopup-title": "Establecer Límite TEP", "shortcut-assign-self": "Asignarte a vos mismo en la tarjeta actual", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtrar mis tarjetas", "shortcut-show-shortcuts": "Traer esta lista de atajos", "shortcut-toggle-filterbar": "Activar/Desactivar Barra Lateral de Filtros", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Activar/Desactivar Barra Lateral de Tableros", "show-cards-minimum-count": "Mostrar cuenta de tarjetas si la lista contiene más que", "sidebar-open": "Abrir Barra Lateral", @@ -481,7 +567,15 @@ "upload": "Cargar", "upload-avatar": "Cargar un avatar", "uploaded-avatar": "Cargado un avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Nombre de usuario", + "import-usernames": "Import Usernames", "view-it": "Verlo", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Seguir", @@ -553,7 +647,8 @@ "minutes": "minutos", "seconds": "segundos", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Si", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Permitir Cambio de Email", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Creado en", + "modifiedAt": "Modified at", "verified": "Verificado", "active": "Activo", "card-received": "Recibido", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Tarjeta", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/es-CL.i18n.json b/i18n/es-CL.i18n.json new file mode 100644 index 000000000..3a69092a3 --- /dev/null +++ b/i18n/es-CL.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Aceptar", + "act-activity-notify": "Notificación de actividad", + "act-addAttachment": "añadido el adjunto __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteAttachment": "eliminado el adjunto __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addSubtask": "añadida la subtarea __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addedLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removedLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklist": "añadida la lista de verificación __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklistItem": "añadido el elemento __checklistItem__ a la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklist": "eliminada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklistItem": "eliminado el elemento __checklistItem__ de la lista de verificación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-checkedItem": "marcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncheckedItem": "desmarcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-completeChecklist": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncompleteChecklist": "no completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createBoard": "creó el tablero __board__", + "act-createSwimlane": "creó el carril de flujo __swimlane__ en el tablero __board__", + "act-createCard": "creada la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createCustomField": "creado el campo personalizado __customField__ en el tablero __board__", + "act-deleteCustomField": "eliminado el campo personalizado __customField__ del tablero __board__", + "act-setCustomField": "editado el campo personalizado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createList": "añadida la lista __list__ al tablero __board__", + "act-addBoardMember": "añadido el mimbro __member__ al tablero __board__", + "act-archivedBoard": "El tablero __board__ se ha archivado", + "act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado", + "act-importBoard": "importado el tablero __board__", + "act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__", + "act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__", + "act-joinMember": "añadido el miembro __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-moveCard": "movida la tarjeta __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__", + "act-moveCardToOtherBoard": "movida la tarjeta __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeBoardMember": "eliminado el miembro __member__ del tablero __board__", + "act-restoredCard": "restaurada la tarjeta __card__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-unjoinMember": "eliminado el miembro __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Acciones", + "activities": "Actividades", + "activity": "Actividad", + "activity-added": "ha añadido %s a %s", + "activity-archived": "%s se ha archivado", + "activity-attached": "ha adjuntado %s a %s", + "activity-created": "ha creado %s", + "activity-customfield-created": "creó el campo personalizado %s", + "activity-excluded": "ha excluido %s de %s", + "activity-imported": "ha importado %s a %s desde %s", + "activity-imported-board": "ha importado %s desde %s", + "activity-joined": "se ha unido a %s", + "activity-moved": "ha movido %s de %s a %s", + "activity-on": "en %s", + "activity-removed": "ha eliminado %s de %s", + "activity-sent": "ha enviado %s a %s", + "activity-unjoined": "se ha desvinculado de %s", + "activity-subtask-added": "ha añadido la subtarea a %s", + "activity-checked-item": "marcado %s en la lista de verificación %s de %s", + "activity-unchecked-item": "desmarcado %s en lista %s de %s", + "activity-checklist-added": "ha añadido una lista de verificación a %s", + "activity-checklist-removed": "eliminada una lista de verificación desde %s", + "activity-checklist-completed": "lista de verificación completada %s de %s", + "activity-checklist-uncompleted": "no completado la lista %s de %s", + "activity-checklist-item-added": "ha añadido el elemento de la lista de verificación a '%s' en %s", + "activity-checklist-item-removed": "eliminado un elemento de la lista de verificación desde '%s' en %s", + "add": "Añadir", + "activity-checked-item-card": "marcado %s en la lista de verificación %s", + "activity-unchecked-item-card": "desmarcado %s en la lista de verificación %s", + "activity-checklist-completed-card": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "activity-checklist-uncompleted-card": "no completó la lista de verificación %s", + "activity-editComment": "comentario editado", + "activity-deleteComment": "comentario eliminado", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Añadir adjunto", + "add-board": "Añadir tablero", + "add-template": "Add Template", + "add-card": "Añadir una tarjeta", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Añadir un carril de flujo", + "add-subtask": "Añadir subtarea", + "add-checklist": "Añadir una lista de verificación", + "add-checklist-item": "Añadir un elemento a la lista de verificación", + "add-cover": "Añadir portada", + "add-label": "Añadir una etiqueta", + "add-list": "Añadir una lista", + "add-members": "Añadir miembros", + "added": "Añadida el", + "addMemberPopup-title": "Miembros", + "admin": "Administrador", + "admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero", + "admin-announcement": "Aviso", + "admin-announcement-active": "Activar el aviso para todo el sistema", + "admin-announcement-title": "Aviso del administrador", + "all-boards": "Tableros", + "and-n-other-card": "y __count__ tarjeta más", + "and-n-other-card_plural": "y otras __count__ tarjetas", + "apply": "Aplicar", + "app-is-offline": "Cargando, espera por favor. Refrescar esta página causará pérdida de datos. Si la carga no funciona, por favor comprueba que el servidor no se ha parado.", + "archive": "Archivar", + "archive-all": "Archivar todo", + "archive-board": "Archivar este tablero", + "archive-card": "Archivar esta tarjeta", + "archive-list": "Archivar esta lista", + "archive-swimlane": "Archivar este carril", + "archive-selection": "Archivar esta selección", + "archiveBoardPopup-title": "¿Archivar este tablero?", + "archived-items": "Archivo", + "archived-boards": "Tableros en el Archivo", + "restore-board": "Restaurar el tablero", + "no-archived-boards": "No hay Tableros en el Archivo", + "archives": "Archivo", + "template": "Plantilla", + "templates": "Plantillas", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Asignar miembros", + "attached": "adjuntado", + "attachment": "Adjunto", + "attachment-delete-pop": "La eliminación de un fichero adjunto es permanente. Esta acción no puede deshacerse.", + "attachmentDeletePopup-title": "¿Eliminar el adjunto?", + "attachments": "Adjuntos", + "auto-watch": "Suscribirse automáticamente a los tableros cuando son creados", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Atrás", + "board-change-color": "Cambiar el color", + "board-nb-stars": "%s destacados", + "board-not-found": "Tablero no encontrado", + "board-private-info": "Este tablero será <strong>privado</strong>.", + "board-public-info": "Este tablero será <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Cambiar el fondo del tablero", + "boardChangeTitlePopup-title": "Renombrar el tablero", + "boardChangeVisibilityPopup-title": "Cambiar visibilidad", + "boardChangeWatchPopup-title": "Cambiar vigilancia", + "boardMenuPopup-title": "Preferencias del tablero", + "boardChangeViewPopup-title": "Vista del tablero", + "boards": "Tableros", + "board-view": "Vista del tablero", + "board-view-cal": "Calendario", + "board-view-swimlanes": "Carriles", + "board-view-collapse": "Contraer", + "board-view-gantt": "Gantt", + "board-view-lists": "Listas", + "bucket-example": "Como “Cosas por hacer” por ejemplo", + "cancel": "Cancelar", + "card-archived": "Se archivó esta tarjeta", + "board-archived": "Se archivó este tablero", + "card-comments-title": "Esta tarjeta tiene %s comentarios.", + "card-delete-notice": "la eliminación es permanente. Perderás todas las acciones asociadas a esta tarjeta.", + "card-delete-pop": "Se eliminarán todas las acciones del historial de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.", + "card-delete-suggest-archive": "Puedes mover una tarjeta al Archivo para quitarla del tablero y preservar la actividad.", + "card-due": "Vence", + "card-due-on": "Vence el", + "card-spent": "Tiempo consumido", + "card-edit-attachments": "Editar los adjuntos", + "card-edit-custom-fields": "Editar los campos personalizados", + "card-edit-labels": "Editar las etiquetas", + "card-edit-members": "Editar los miembros", + "card-labels-title": "Cambia las etiquetas de la tarjeta", + "card-members-title": "Añadir o eliminar miembros del tablero desde la tarjeta.", + "card-start": "Comienza", + "card-start-on": "Comienza el", + "cardAttachmentsPopup-title": "Adjuntar desde", + "cardCustomField-datePopup-title": "Cambiar la fecha", + "cardCustomFieldsPopup-title": "Editar los campos personalizados", + "cardStartVotingPopup-title": "Comience a votar", + "positiveVoteMembersPopup-title": "Favorables", + "negativeVoteMembersPopup-title": "Contrarios", + "card-edit-voting": "Editar votación", + "editVoteEndDatePopup-title": "Cambie fecha de termino del voto", + "allowNonBoardMembers": "Permitir todos los usuarios autentificados", + "vote-question": "Pregunta de votación", + "vote-public": "Mostrar quien voto que", + "vote-for-it": "por esto", + "vote-against": "contrarios", + "deleteVotePopup-title": "¿Borrar voto?", + "vote-delete-pop": "El Borrado es permanente. Perderá todas las acciones asociadas con este voto.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "¿Eliminar la tarjeta?", + "cardDetailsActionsPopup-title": "Acciones de la tarjeta", + "cardLabelsPopup-title": "Etiquetas", + "cardMembersPopup-title": "Miembros", + "cardMorePopup-title": "Más", + "cardTemplatePopup-title": "Crear plantilla", + "cards": "Tarjetas", + "cards-count": "Tarjetas", + "cards-count-one": "Tarjeta", + "casSignIn": "Iniciar sesión con CAS", + "cardType-card": "Tarjeta", + "cardType-linkedCard": "Tarjeta enlazada", + "cardType-linkedBoard": "Tablero enlazado", + "change": "Cambiar", + "change-avatar": "Cambiar el avatar", + "change-password": "Cambiar la contraseña", + "change-permissions": "Cambiar los permisos", + "change-settings": "Cambiar las preferencias", + "changeAvatarPopup-title": "Cambiar el avatar", + "changeLanguagePopup-title": "Cambiar el idioma", + "changePasswordPopup-title": "Cambiar la contraseña", + "changePermissionsPopup-title": "Cambiar los permisos", + "changeSettingsPopup-title": "Cambiar las preferencias", + "subtasks": "Subtareas", + "checklists": "Lista de verificación", + "click-to-star": "Haz clic para destacar este tablero.", + "click-to-unstar": "Haz clic para dejar de destacar este tablero.", + "clipboard": "el portapapeles o con arrastrar y soltar", + "close": "Cerrar", + "close-board": "Cerrar el tablero", + "close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.", + "close-card": "Close Card", + "color-black": "negra", + "color-blue": "azul", + "color-crimson": "carmesí", + "color-darkgreen": "verde oscuro", + "color-gold": "oro", + "color-gray": "gris", + "color-green": "verde", + "color-indigo": "añil", + "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "rosa claro", + "color-navy": "azul marino", + "color-orange": "naranja", + "color-paleturquoise": "turquesa", + "color-peachpuff": "melocotón", + "color-pink": "rosa", + "color-plum": "púrpura", + "color-purple": "violeta", + "color-red": "roja", + "color-saddlebrown": "marrón", + "color-silver": "plata", + "color-sky": "celeste", + "color-slateblue": "azul", + "color-white": "blanco", + "color-yellow": "amarilla", + "unset-color": "Desmarcar", + "comment": "Comentar", + "comment-placeholder": "Escribir comentario", + "comment-only": "Sólo comentarios", + "comment-only-desc": "Solo puedes comentar en las tarjetas.", + "no-comments": "No hay comentarios", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", + "worker": "Trabajador", + "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.", + "computer": "el ordenador", + "confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?", + "confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?", + "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", + "linkCardPopup-title": "Enlazar tarjeta", + "searchElementPopup-title": "Buscar", + "copyCardPopup-title": "Copiar la tarjeta", + "copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de verificación en varias tarjetas", + "copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", + "create": "Crear", + "createBoardPopup-title": "Crear tablero", + "chooseBoardSourcePopup-title": "Importar un tablero", + "createLabelPopup-title": "Crear etiqueta", + "createCustomField": "Crear un campo", + "createCustomFieldPopup-title": "Crear un campo", + "current": "actual", + "custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "custom-field-checkbox": "Casilla de verificación", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Fecha", + "custom-field-dropdown": "Lista desplegable", + "custom-field-dropdown-none": "(nada)", + "custom-field-dropdown-options": "Opciones de la lista", + "custom-field-dropdown-options-placeholder": "Pulsa Intro para añadir más opciones", + "custom-field-dropdown-unknown": "(desconocido)", + "custom-field-number": "Número", + "custom-field-text": "Texto", + "custom-fields": "Campos personalizados", + "date": "Fecha", + "decline": "Declinar", + "default-avatar": "Avatar por defecto", + "delete": "Eliminar", + "deleteCustomFieldPopup-title": "¿Eliminar el campo personalizado?", + "deleteLabelPopup-title": "¿Eliminar la etiqueta?", + "description": "Descripción", + "disambiguateMultiLabelPopup-title": "Desambiguar la acción de etiqueta", + "disambiguateMultiMemberPopup-title": "Desambiguar la acción de miembro", + "discard": "Descartarla", + "done": "Hecho", + "download": "Descargar", + "edit": "Editar", + "edit-avatar": "Cambiar el avatar", + "edit-profile": "Editar el perfil", + "edit-wip-limit": "Cambiar el límite del trabajo en proceso", + "soft-wip-limit": "Límite del trabajo en proceso flexible", + "editCardStartDatePopup-title": "Cambiar la fecha de comienzo", + "editCardDueDatePopup-title": "Cambiar la fecha de vencimiento", + "editCustomFieldPopup-title": "Editar el campo", + "editCardSpentTimePopup-title": "Cambiar el tiempo consumido", + "editLabelPopup-title": "Cambiar la etiqueta", + "editNotificationPopup-title": "Editar las notificaciones", + "editProfilePopup-title": "Editar el perfil", + "email": "Correo electrónico", + "email-enrollAccount-subject": "Cuenta creada en __siteName__", + "email-enrollAccount-text": "Hola __user__,\n\nPara empezar a utilizar el servicio, simplemente haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-fail": "Error al enviar el correo", + "email-fail-text": "Error al intentar enviar el correo", + "email-invalid": "Correo no válido", + "email-invite": "Invitar vía correo electrónico", + "email-invite-subject": "__inviter__ ha enviado una invitación", + "email-invite-text": "Estimado __user__,\n\n__inviter__ te invita a unirte al tablero '__board__' para colaborar.\n\nPor favor, haz clic en el siguiente enlace:\n\n__url__\n\nGracias.", + "email-resetPassword-subject": "Restablecer tu contraseña en __siteName__", + "email-resetPassword-text": "Hola __user__,\n\nPara restablecer tu contraseña, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-sent": "Correo enviado", + "email-verifyEmail-subject": "Verifica tu dirección de correo en __siteName__", + "email-verifyEmail-text": "Hola __user__,\n\nPara verificar tu cuenta de correo electrónico, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "enable-wip-limit": "Habilitar el límite del trabajo en proceso", + "error-board-doesNotExist": "El tablero no existe", + "error-board-notAdmin": "Es necesario ser administrador de este tablero para hacer eso", + "error-board-notAMember": "Es necesario ser miembro de este tablero para hacer eso", + "error-json-malformed": "El texto no es un JSON válido", + "error-json-schema": "Sus datos JSON no incluyen la información apropiada en el formato correcto", + "error-csv-schema": "Su CSV(Valores separados por coma)/TSV(Valores separados por tab) no incluyen la información apropiada en el formato correcto", + "error-list-doesNotExist": "La lista no existe", + "error-user-doesNotExist": "El usuario no existe", + "error-user-notAllowSelf": "No puedes invitarte a ti mismo", + "error-user-notCreated": "El usuario no ha sido creado", + "error-username-taken": "Este nombre de usuario ya está en uso", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Esta dirección de correo ya está en uso", + "export-board": "Exportar el tablero", + "export-board-json": "Exportar tablero a JSON", + "export-board-csv": "Exportar tablero a CSV", + "export-board-tsv": "Exportar tablero a TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar el tablero", + "exportCardPopup-title": "Export card", + "sort": "Ordenar", + "sort-desc": "Click para ordenar la lista", + "list-sort-by": "Ordenar la lista por:", + "list-label-modifiedAt": "Hora de último acceso", + "list-label-title": "Nombre de la lista", + "list-label-sort": "Tu orden manual", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filtrar", + "filter-cards": "Filtrar tarjetas o listas", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filtrar listas por título", + "filter-clear": "Limpiar el filtro", + "filter-labels-label": "Filter by label", + "filter-no-label": "Sin etiqueta", + "filter-member-label": "Filter by member", + "filter-no-member": "Sin miembro", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No asignado", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "Sin campos personalizados", + "filter-show-archive": "Mostrar las listas archivadas", + "filter-hide-empty": "Ocultar las listas vacías", + "filter-on": "Filtrado activado", + "filter-on-desc": "Estás filtrando tarjetas en este tablero. Haz clic aquí para editar el filtro.", + "filter-to-selection": "Filtrar la selección", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Filtrado avanzado", + "advanced-filter-description": "El filtrado avanzado permite escribir una cadena que contiene los siguientes operadores: == != <= >= && || ( ) Se utiliza un espacio como separador entre los operadores. Se pueden filtrar todos los campos personalizados escribiendo sus nombres y valores. Por ejemplo: Campo1 == Valor1. Nota: Si los campos o valores contienen espacios, deben encapsularse entre comillas simples. Por ejemplo: 'Campo 1' == 'Valor 1'. Para omitir los caracteres de control único (' \\/), se usa \\. Por ejemplo: Campo1 = I\\'m. También se pueden combinar múltiples condiciones. Por ejemplo: C1 == V1 || C1 == V2. Normalmente todos los operadores se interpretan de izquierda a derecha. Se puede cambiar el orden colocando paréntesis. Por ejemplo: C1 == V1 && ( C2 == V2 || C2 == V3 ). También se puede buscar en campos de texto usando expresiones regulares: C1 == /Tes.*/i", + "fullname": "Nombre completo", + "header-logo-title": "Volver a tu página de tableros", + "hide-system-messages": "Ocultar las notificaciones de actividad", + "headerBarCreateBoardPopup-title": "Crear tablero", + "home": "Inicio", + "import": "Importar", + "impersonate-user": "Impersonate user", + "link": "Enlace", + "import-board": "importar un tablero", + "import-board-c": "Importar un tablero", + "import-board-title-trello": "Importar un tablero desde Trello", + "import-board-title-wekan": "Importar tablero desde una exportación previa", + "import-board-title-csv": "Importar tablero desde CSV/TSV", + "from-trello": "Desde Trello", + "from-wekan": "Desde exportación previa", + "from-csv": "Desde CSV/TSV", + "import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego 'Más' > 'Imprimir y exportar' > 'Exportar JSON', y copia el texto resultante.", + "import-board-instruction-csv": "Pegue en sus Valores separados por coma(CSV)/Valores separados por tab(TSV).", + "import-board-instruction-wekan": "En tu tablero, vete a 'Menú', luego 'Exportar tablero', y copia el texto en el archivo descargado.", + "import-board-instruction-about-errors": "Aunque obtengas errores cuando importes el tablero, a veces la importación funciona igualmente, y el tablero se encontrará en la página de tableros.", + "import-json-placeholder": "Pega tus datos JSON válidos aquí", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Mapa de miembros", + "import-members-map": "Tu tablero importado tiene algunos miembros. Por favor, mapea los miembros que quieres importar con tus usuarios.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Revisión de la asignación de miembros", + "import-user-select": "Selecciona el miembro existe que quieres usar como este miembro.", + "importMapMembersAddPopup-title": "Seleccionar miembro", + "info": "Versión", + "initials": "Iniciales", + "invalid-date": "Fecha no válida", + "invalid-time": "Tiempo no válido", + "invalid-user": "Usuario no válido", + "joined": "se ha unido", + "just-invited": "Has sido invitado a este tablero", + "keyboard-shortcuts": "Atajos de teclado", + "label-create": "Crear una etiqueta", + "label-default": "etiqueta %s (por defecto)", + "label-delete-pop": "Se eliminará esta etiqueta de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "labels": "Etiquetas", + "language": "Cambiar el idioma", + "last-admin-desc": "No puedes cambiar roles porque debe haber al menos un administrador.", + "leave-board": "Abandonar el tablero", + "leave-board-pop": "¿Seguro que quieres abandonar __boardTitle__? Serás desvinculado de todas las tarjetas en este tablero.", + "leaveBoardPopup-title": "¿Abandonar el tablero?", + "link-card": "Enlazar a esta tarjeta", + "list-archive-cards": "Archivar todas las tarjetas de esta lista", + "list-archive-cards-pop": "Esto eliminará del tablero todas las tarjetas en esta lista. Para ver las tarjetas en el Archivo y recuperarlas al tablero haga click en \"Menu\" > \"Archivo\"", + "list-move-cards": "Mover todas las tarjetas de esta lista", + "list-select-cards": "Seleccionar todas las tarjetas de esta lista", + "set-color-list": "Cambiar el color", + "listActionPopup-title": "Acciones de la lista", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Acciones del carril de flujo", + "swimlaneAddPopup-title": "Añadir un carril de flujo debajo", + "listImportCardPopup-title": "Importar una tarjeta de Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "Más", + "link-list": "Enlazar a esta lista", + "list-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar la lista. Esta acción no puede deshacerse.", + "list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.", + "lists": "Listas", + "swimlanes": "Carriles", + "log-out": "Finalizar la sesión", + "log-in": "Iniciar sesión", + "loginPopup-title": "Iniciar sesión", + "memberMenuPopup-title": "Preferencias de miembro", + "members": "Miembros", + "menu": "Menú", + "move-selection": "Mover la selección", + "moveCardPopup-title": "Mover la tarjeta", + "moveCardToBottom-title": "Mover al final", + "moveCardToTop-title": "Mover al principio", + "moveSelectionPopup-title": "Mover la selección", + "multi-selection": "Selección múltiple", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Selección múltiple activada", + "muted": "Silenciado", + "muted-info": "No serás notificado de ningún cambio en este tablero", + "my-boards": "Mis tableros", + "name": "Nombre", + "no-archived-cards": "No hay tarjetas archivadas.", + "no-archived-lists": "No hay listas archivadas.", + "no-archived-swimlanes": "No hay carriles archivados.", + "no-results": "Sin resultados", + "normal": "Normal", + "normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.", + "not-accepted-yet": "La invitación no ha sido aceptada aún", + "notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participas como creador o miembro", + "notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando", + "optional": "opcional", + "or": "o", + "page-maybe-private": "Esta página puede ser privada. Es posible que puedas verla al <a href='%s'>iniciar sesión</a>.", + "page-not-found": "Página no encontrada.", + "password": "Contraseña", + "paste-or-dragdrop": "pegar o arrastrar y soltar un fichero de imagen (sólo imagen)", + "participating": "Participando", + "preview": "Previsualizar", + "previewAttachedImagePopup-title": "Previsualizar", + "previewClipboardImagePopup-title": "Previsualizar", + "private": "Privado", + "private-desc": "Este tablero es privado. Sólo las personas añadidas al tablero pueden verlo y editarlo.", + "profile": "Perfil", + "public": "Público", + "public-desc": "Este tablero es público. Es visible para cualquiera a través del enlace, y se mostrará en los buscadores como Google. Sólo las personas añadidas al tablero pueden editarlo.", + "quick-access-description": "Destaca un tablero para añadir un acceso directo en esta barra.", + "remove-cover": "Eliminar portada", + "remove-from-board": "Desvincular del tablero", + "remove-label": "Eliminar la etiqueta", + "listDeletePopup-title": "¿Eliminar la lista?", + "remove-member": "Eliminar miembro", + "remove-member-from-card": "Eliminar de la tarjeta", + "remove-member-pop": "¿Eliminar __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero. En ellas se mostrará una notificación.", + "removeMemberPopup-title": "¿Eliminar miembro?", + "rename": "Renombrar", + "rename-board": "Renombrar el tablero", + "restore": "Restaurar", + "save": "Añadir", + "search": "Buscar", + "rules": "Reglas", + "search-cards": "Buscar entre los títulos, las descripciones de las tarjetas/listas y los campos personalizados en este tablero.", + "search-example": "Write text you search and press Enter", + "select-color": "Seleccionar el color", + "select-board": "Select Board", + "set-wip-limit-value": "Cambiar el límite para el número máximo de tareas en esta lista.", + "setWipLimitPopup-title": "Cambiar el límite del trabajo en proceso", + "shortcut-assign-self": "Asignarte a ti mismo a la tarjeta actual", + "shortcut-autocomplete-emoji": "Autocompletar emoji", + "shortcut-autocomplete-members": "Autocompletar miembros", + "shortcut-clear-filters": "Limpiar todos los filtros", + "shortcut-close-dialog": "Cerrar el cuadro de diálogo", + "shortcut-filter-my-cards": "Filtrar mis tarjetas", + "shortcut-show-shortcuts": "Mostrar esta lista de atajos", + "shortcut-toggle-filterbar": "Conmutar la barra lateral del filtro", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Conmutar la barra lateral del tablero", + "show-cards-minimum-count": "Mostrar recuento de tarjetas si la lista contiene más de", + "sidebar-open": "Abrir la barra lateral", + "sidebar-close": "Cerrar la barra lateral", + "signupPopup-title": "Crear una cuenta", + "star-board-title": "Haz clic para destacar este tablero. Se mostrará en la parte superior de tu lista de tableros.", + "starred-boards": "Tableros destacados", + "starred-boards-description": "Los tableros destacados se mostrarán en la parte superior de tu lista de tableros.", + "subscribe": "Suscribirse", + "team": "Equipo", + "this-board": "este tablero", + "this-card": "esta tarjeta", + "spent-time-hours": "Tiempo consumido (horas)", + "overtime-hours": "Tiempo excesivo (horas)", + "overtime": "Tiempo excesivo", + "has-overtime-cards": "Hay tarjetas con el tiempo excedido", + "has-spenttime-cards": "Se ha excedido el tiempo de las tarjetas", + "time": "Hora", + "title": "Título", + "tracking": "Siguiendo", + "tracking-info": "Serás notificado de cualquier cambio en las tarjetas en las que participas como creador o miembro.", + "type": "Tipo", + "unassign-member": "Desvincular al miembro", + "unsaved-description": "Tienes una descripción por añadir.", + "unwatch": "Dejar de vigilar", + "upload": "Cargar", + "upload-avatar": "Cargar un avatar", + "uploaded-avatar": "Avatar cargado", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Nombre de usuario", + "import-usernames": "Import Usernames", + "view-it": "Verla", + "warn-list-archived": "advertencia: esta tarjeta está en una lista en el Archivo", + "watch": "Vigilar", + "watching": "Vigilando", + "watching-info": "Serás notificado de cualquier cambio en este tablero", + "welcome-board": "Tablero de bienvenida", + "welcome-swimlane": "Hito 1", + "welcome-list1": "Básicos", + "welcome-list2": "Avanzados", + "card-templates-swimlane": "Plantilla de tarjeta", + "list-templates-swimlane": "Listar plantillas", + "board-templates-swimlane": "Plantilla de tablero", + "what-to-do": "¿Qué quieres hacer?", + "wipLimitErrorPopup-title": "El límite del trabajo en proceso no es válido.", + "wipLimitErrorPopup-dialog-pt1": "El número de tareas en esta lista es mayor que el límite del trabajo en proceso que has definido.", + "wipLimitErrorPopup-dialog-pt2": "Por favor, mueve algunas tareas fuera de esta lista, o fija un límite del trabajo en proceso más alto.", + "admin-panel": "Panel del administrador", + "settings": "Preferencias", + "people": "Personas", + "registration": "Registro", + "disable-self-registration": "Deshabilitar autoregistro", + "invite": "Invitar", + "invite-people": "Invitar a personas", + "to-boards": "A el(los) tablero(s)", + "email-addresses": "Direcciones de correo electrónico", + "smtp-host-description": "Dirección del servidor SMTP para gestionar tus correos", + "smtp-port-description": "Puerto usado por el servidor SMTP para mandar correos", + "smtp-tls-description": "Habilitar el soporte TLS para el servidor SMTP", + "smtp-host": "Servidor SMTP", + "smtp-port": "Puerto SMTP", + "smtp-username": "Nombre de usuario", + "smtp-password": "Contraseña", + "smtp-tls": "Soporte TLS", + "send-from": "Desde", + "send-smtp-test": "Enviarte un correo de prueba a ti mismo", + "invitation-code": "Código de Invitación", + "email-invite-register-subject": "__inviter__ te ha enviado una invitación", + "email-invite-register-text": "Querido __user__,\n__inviter__ le invita al tablero kanban para colaborar.\n\nPor favor, siga el siguiente enlace:\n__url__\n\nY tu código de invitación es: __icode__\n\nGracias.", + "email-smtp-test-subject": "Prueba de email SMTP", + "email-smtp-test-text": "El correo se ha enviado correctamente", + "error-invitation-code-not-exist": "El código de invitación no existe", + "error-notAuthorized": "No estás autorizado a ver esta página.", + "webhook-title": "Nombre del Webhook", + "webhook-token": "Token (opcional para la autenticación)", + "outgoing-webhooks": "Webhooks salientes", + "bidirectional-webhooks": "Webhooks de doble sentido", + "outgoingWebhooksPopup-title": "Webhooks salientes", + "boardCardTitlePopup-title": "Filtro de títulos de tarjeta", + "disable-webhook": "Deshabilitar este Webhook", + "global-webhook": "Webhooks globales", + "new-outgoing-webhook": "Nuevo webhook saliente", + "no-name": "(Desconocido)", + "Node_version": "Versión de Node", + "Meteor_version": "Versión de Meteor", + "MongoDB_version": "Versión de MongoDB", + "MongoDB_storage_engine": "Motor de almacenamiento de MongoDB", + "MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado", + "OS_Arch": "Arquitectura del sistema", + "OS_Cpus": "Número de CPUs del sistema", + "OS_Freemem": "Memoria libre del sistema", + "OS_Loadavg": "Carga media del sistema", + "OS_Platform": "Plataforma del sistema", + "OS_Release": "Publicación del sistema", + "OS_Totalmem": "Memoria total del sistema", + "OS_Type": "Tipo de sistema", + "OS_Uptime": "Tiempo activo del sistema", + "days": "días", + "hours": "horas", + "minutes": "minutos", + "seconds": "segundos", + "show-field-on-card": "Mostrar este campo en la tarjeta", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Mostrar etiquetas de campos en la minitarjeta.", + "yes": "Sí", + "no": "No", + "accounts": "Cuentas", + "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", + "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", + "createdAt": "Fecha de alta", + "modifiedAt": "Modified at", + "verified": "Verificado", + "active": "Activo", + "card-received": "Recibido", + "card-received-on": "Recibido el", + "card-end": "Finalizado", + "card-end-on": "Finalizado el", + "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", + "editCardEndDatePopup-title": "Cambiar la fecha de finalización", + "setCardColorPopup-title": "Cambiar el color", + "setCardActionsColorPopup-title": "Elegir un color", + "setSwimlaneColorPopup-title": "Elegir un color", + "setListColorPopup-title": "Elegir un color", + "assigned-by": "Asignado por", + "requested-by": "Solicitado por", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", + "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", + "boardDeletePopup-title": "¿Eliminar el tablero?", + "delete-board": "Eliminar el tablero", + "default-subtasks-board": "Subtareas para el tablero __board__", + "default": "Por defecto", + "queue": "Cola", + "subtask-settings": "Preferencias de las subtareas", + "card-settings": "Preferencias de la tarjeta", + "boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero", + "boardCardSettingsPopup-title": "Preferencias de la tarjeta", + "deposit-subtasks-board": "Depositar subtareas en este tablero:", + "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:", + "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:", + "prefix-with-full-path": "Prefijo con ruta completa", + "prefix-with-parent": "Prefijo con el padre", + "subtext-with-full-path": "Subtexto con ruta completa", + "subtext-with-parent": "Subtexto con el padre", + "change-card-parent": "Cambiar la tarjeta padre", + "parent-card": "Tarjeta padre", + "source-board": "Tablero de origen", + "no-parent": "No mostrar la tarjeta padre", + "activity-added-label": "añadida etiqueta %s a %s", + "activity-removed-label": "eliminada etiqueta '%s' desde %s", + "activity-delete-attach": "eliminado un adjunto desde %s", + "activity-added-label-card": "añadida etiqueta '%s'", + "activity-removed-label-card": "eliminada etiqueta '%s'", + "activity-delete-attach-card": "eliminado un adjunto", + "activity-set-customfield": "Cambiar el campo personalizado '%s' a '%s' en %s", + "activity-unset-customfield": "Desmarcar el campo personalizado '%s' en %s", + "r-rule": "Regla", + "r-add-trigger": "Añadir disparador", + "r-add-action": "Añadir acción", + "r-board-rules": "Reglas del tablero", + "r-add-rule": "Añadir regla", + "r-view-rule": "Ver regla", + "r-delete-rule": "Eliminar regla", + "r-new-rule-name": "Nueva título de regla", + "r-no-rules": "No hay reglas", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "Cuando una tarjeta", + "r-is": "es", + "r-is-moved": "es movida", + "r-added-to": "Added to", + "r-removed-from": "eliminado de", + "r-the-board": "el tablero", + "r-list": "la lista", + "list": "List", + "set-filter": "Filtrar", + "r-moved-to": "Movido a", + "r-moved-from": "Movido desde", + "r-archived": "Se archivó", + "r-unarchived": "Restaurado del archivo", + "r-a-card": "una tarjeta", + "r-when-a-label-is": "Cuando una etiqueta es", + "r-when-the-label": "Cuando la etiqueta es", + "r-list-name": "Nombre de lista", + "r-when-a-member": "Cuando un miembro es", + "r-when-the-member": "Cuando el miembro", + "r-name": "nombre", + "r-when-a-attach": "Cuando un adjunto", + "r-when-a-checklist": "Cuando una lista de verificación es", + "r-when-the-checklist": "Cuando la lista de verificación", + "r-completed": "Completada", + "r-made-incomplete": "Hecha incompleta", + "r-when-a-item": "Cuando un elemento de la lista de verificación es", + "r-when-the-item": "Cuando el elemento de la lista de verificación es", + "r-checked": "Marcado", + "r-unchecked": "Desmarcado", + "r-move-card-to": "Mover la tarjeta", + "r-top-of": "Arriba de", + "r-bottom-of": "Abajo de", + "r-its-list": "su lista", + "r-archive": "Archivar", + "r-unarchive": "Restaurar del Archivo", + "r-card": "la tarjeta", + "r-add": "Añadir", + "r-remove": "Eliminar", + "r-label": "etiqueta", + "r-member": "miembro", + "r-remove-all": "Eliminar todos los miembros de la tarjeta", + "r-set-color": "Cambiar el color a", + "r-checklist": "lista de verificación", + "r-check-all": "Marcar todo", + "r-uncheck-all": "Desmarcar todo", + "r-items-check": "elementos de la lista de verificación", + "r-check": "Marcar", + "r-uncheck": "Desmarcar", + "r-item": "elemento", + "r-of-checklist": "de la lista de verificación", + "r-send-email": "Enviar un email", + "r-to": "a", + "r-of": "of", + "r-subject": "asunto", + "r-rule-details": "Detalle de la regla", + "r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista", + "r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista", + "r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista", + "r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista", + "r-d-send-email": "Enviar email", + "r-d-send-email-to": "a", + "r-d-send-email-subject": "asunto", + "r-d-send-email-message": "mensaje", + "r-d-archive": "Archivar la tarjeta", + "r-d-unarchive": "Restaurar tarjeta del Archivo", + "r-d-add-label": "Añadir etiqueta", + "r-d-remove-label": "Eliminar etiqueta", + "r-create-card": "Crear una nueva tarjeta", + "r-in-list": "en la lista", + "r-in-swimlane": "en el carril", + "r-d-add-member": "Añadir miembro", + "r-d-remove-member": "Eliminar miembro", + "r-d-remove-all-member": "Eliminar todos los miembros", + "r-d-check-all": "Marcar todos los elementos de una lista", + "r-d-uncheck-all": "Desmarcar todos los elementos de una lista", + "r-d-check-one": "Marcar elemento", + "r-d-uncheck-one": "Desmarcar elemento", + "r-d-check-of-list": "de la lista de verificación", + "r-d-add-checklist": "Añadir una lista de verificación", + "r-d-remove-checklist": "Eliminar lista de verificación", + "r-by": "por", + "r-add-checklist": "Añadir una lista de verificación", + "r-with-items": "con items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Agregar el carril", + "r-swimlane-name": "nombre del carril", + "r-board-note": "Nota: deje un campo vacío para que coincida con todos los valores posibles", + "r-checklist-note": "Nota: los ítems de la lista tienen que escribirse como valores separados por coma.", + "r-when-a-card-is-moved": "Cuando una tarjeta es movida a otra lista", + "r-set": "Cambiar", + "r-update": "Actualizar", + "r-datefield": "campo de fecha", + "r-df-start-at": "comienza", + "r-df-due-at": "vencimiento", + "r-df-end-at": "finalizado", + "r-df-received-at": "recibido", + "r-to-current-datetime": "a la fecha/hora actual", + "r-remove-value-from": "Eliminar el valor de", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Método de autenticación", + "authentication-type": "Tipo de autenticación", + "custom-product-name": "Nombre de producto personalizado", + "layout": "Diseño", + "hide-logo": "Ocultar el logo", + "add-custom-html-after-body-start": "Añade HTML personalizado después de <body>", + "add-custom-html-before-body-end": "Añade HTML personalizado después de </body>", + "error-undefined": "Algo no está bien", + "error-ldap-login": "Ocurrió un error al intentar acceder", + "display-authentication-method": "Mostrar el método de autenticación", + "default-authentication-method": "Método de autenticación por defecto", + "duplicate-board": "Duplicar tablero", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "El número de personas es:", + "swimlaneDeletePopup-title": "¿Eliminar el carril de flujo?", + "swimlane-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar el carril de flujo. Esta acción no puede deshacerse.", + "restore-all": "Restaurar todas", + "delete-all": "Borrar todas", + "loading": "Cargando. Por favor, espere.", + "previous_as": "el último tiempo fue", + "act-a-dueAt": "cambiada la hora de vencimiento a \nCuándo: __timeValue__\nDónde: __card__\n el vencimiento anterior fue __timeOldValue__", + "act-a-endAt": "cambiada la hora de finalización a __timeValue__ Fecha anterior: (__timeOldValue__)", + "act-a-startAt": "cambiada la hora de comienzo a __timeValue__ Fecha anterior: (__timeOldValue__)", + "act-a-receivedAt": "cambiada la fecha de recepción a __timeValue__ Fecha anterior: (__timeOldValue__)", + "a-dueAt": "cambiada la hora de vencimiento a", + "a-endAt": "cambiada la hora de finalización a", + "a-startAt": "cambiada la hora de comienzo a", + "a-receivedAt": "cambiada la hora de recepción a", + "almostdue": "está próxima la hora de vencimiento actual %s", + "pastdue": "se sobrepasó la hora de vencimiento actual%s", + "duenow": "la hora de vencimiento actual %s es hoy", + "act-newDue": "__list__/__card__ tiene una 1ra notificación de vencimiento [__board__]", + "act-withDue": "__list__/__card__ notificaciones de vencimiento [__board__]", + "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", + "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", + "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", + "act-atUserComment": "Se te mencionó en [__board__] __list__/__card__", + "delete-user-confirm-popup": "¿Seguro que quieres eliminar esta cuenta? Esta acción no puede deshacerse.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta", + "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta", + "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio", + "assignee": "Asignado", + "cardAssigneesPopup-title": "Asignado", + "addmore-detail": "Añadir una descripción detallada", + "show-on-card": "Mostrar en la tarjeta", + "new": "Nuevo", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Editar el usuario", + "newUserPopup-title": "Nuevo usuario", + "notifications": "Notificaciones", + "view-all": "Ver todo", + "filter-by-unread": "Filtrar por no leído", + "mark-all-as-read": "Marcar todo como leido", + "remove-all-read": "Remove all read", + "allow-rename": "Permitir renombrar", + "allowRenamePopup-title": "Permitir renombrar", + "start-day-of-week": "Set day of the week start", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Tarjeta", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "la lista", + "operator-list-abbrev": "l", + "operator-label": "etiqueta", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "miembro", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "vencimiento", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "vencimiento", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "lista de verificación", + "predicate-start": "comienza", + "predicate-end": "finalizado", + "predicate-assignee": "assignee", + "predicate-member": "miembro", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/es-LA.i18n.json b/i18n/es-LA.i18n.json new file mode 100644 index 000000000..f596968c0 --- /dev/null +++ b/i18n/es-LA.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Accept", + "act-activity-notify": "Activity Notification", + "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "created board __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Actions", + "activities": "Activities", + "activity": "Activity", + "activity-added": "added %s to %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/es-MX.i18n.json b/i18n/es-MX.i18n.json new file mode 100644 index 000000000..3b1175a3b --- /dev/null +++ b/i18n/es-MX.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Aceptar", + "act-activity-notify": "Notificación de actividad", + "act-addAttachment": "adjunto agregado __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteAttachment": "adjunto eliminado __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addSubtask": "subtarea agregada __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addLabel": "Etiqueta agregada __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addedLabel": "Etiqueta agregada __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeLabel": "Etiqueta eliminada __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removedLabel": "Etiqueta eliminada __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklist": "lista de verificación agregada __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklistItem": "elemento agregado __checklistItem__ a la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklist": "lista de verificación eliminada __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklistItem": "elemento eliminado __checklistItem__ de la lista de verificación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-checkedItem": "elemento marcado __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncheckedItem": "elemento desmarcado __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-completeChecklist": "lista de verificación completada __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncompleteChecklist": "lista de verificación no completada __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createBoard": "tablero creado __board__", + "act-createSwimlane": "carril de flujo creado __swimlane__ en el tablero __board__", + "act-createCard": "tarjeta creada __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createCustomField": "campo personalizado creado __customField__ en el tablero __board__", + "act-deleteCustomField": "campo personalizado eliminado __customField__ del tablero __board__", + "act-setCustomField": "campo personalizado editado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createList": "la lista agregada __list__ al tablero __board__", + "act-addBoardMember": "miembro agregado __member__ al tablero __board__", + "act-archivedBoard": "El tablero __board__ se ha archivado", + "act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado", + "act-importBoard": "tablero importado __board__", + "act-importCard": "tarjeta importada __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__", + "act-importList": "lista importada __list__ al carril __swimlane__ del tablero __board__", + "act-joinMember": "miembro agregado __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-moveCard": "tarjeta movida __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__", + "act-moveCardToOtherBoard": "tarjeta movida __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeBoardMember": "miembro eliminado __member__ del tablero __board__", + "act-restoredCard": "tarjeta restaurada __card__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-unjoinMember": "miembro eliminado __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Acciones", + "activities": "Actividades", + "activity": "Actividad", + "activity-added": "ha agregado %s a %s", + "activity-archived": "%s archivado", + "activity-attached": "ha adjuntado %s de %s", + "activity-created": "ha creado %s", + "activity-customfield-created": "campo personalizado creado %s", + "activity-excluded": "ha excluido %s de %s", + "activity-imported": "ha importado %s a %s desde %s", + "activity-imported-board": "ha importado %s desde %s", + "activity-joined": "se ha unido %s", + "activity-moved": "ha movido %s de %s a %s", + "activity-on": "en %s", + "activity-removed": "ha eliminado %s de %s", + "activity-sent": "ha enviado %s a %s", + "activity-unjoined": "se ha desvinculado de %s", + "activity-subtask-added": "ha agregado la subtarea a %s", + "activity-checked-item": "marcado %s en la lista de verificación %s de %s", + "activity-unchecked-item": "desmarcado %s en lista de verificación %s de %s", + "activity-checklist-added": "ha agregado una lista de verificación a %s", + "activity-checklist-removed": "ha eliminado una lista de verificación de %s", + "activity-checklist-completed": "lista de verificación completada %s de %s", + "activity-checklist-uncompleted": "no completado la lista de verificación %s de %s", + "activity-checklist-item-added": "ha agregado el elemento de la lista de verificación a '%s' en %s", + "activity-checklist-item-removed": "elemento de la lista de verificación eliminado desde '%s' en %s", + "add": "Agregar", + "activity-checked-item-card": "marcado %s en la lista de verificación %s", + "activity-unchecked-item-card": "desmarcado %s en la lista de verificación %s", + "activity-checklist-completed-card": "lista de verificación completada __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "activity-checklist-uncompleted-card": "no completó la lista de verificación %s", + "activity-editComment": "comentario editado %s", + "activity-deleteComment": "comentario eliminado %s", + "activity-receivedDate": "fecha de recepción editada para %s de %s", + "activity-startDate": "fecha de inicio editada a %s de %s", + "activity-dueDate": "fecha de vencimiento editada a %s de %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Agregar", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/es-PE.i18n.json b/i18n/es-PE.i18n.json new file mode 100644 index 000000000..db3ca9a2d --- /dev/null +++ b/i18n/es-PE.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Aceptar", + "act-activity-notify": "Notificación de actividad", + "act-addAttachment": "agregado el adjunto __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteAttachment": "eliminado el adjunto __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addSubtask": "agregada la subtarea __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addLabel": "agregada la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addedLabel": "agregada la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removedLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklist": "agregada la lista de comprobación __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklistItem": "agregado el elemento __checklistItem__ a la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklist": "eliminada la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklistItem": "eliminado el elemento __checklistItem__ de la lista de comprobación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-checkedItem": "marcado el elemento __checklistItem__ de la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncheckedItem": "desmarcado el elemento __checklistItem__ de la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-completeChecklist": "completada la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncompleteChecklist": "no completada la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createBoard": "creó el tablero __board__", + "act-createSwimlane": "creó el carril «swimlane» __swimlane__ en el tablero __board__", + "act-createCard": "creada la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createCustomField": "creado el campo personalizado __customField__ en el tablero __board__", + "act-deleteCustomField": "eliminado el campo personalizado __customField__ del tablero __board__", + "act-setCustomField": "editado el campo personalizado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createList": "agregada la lista __list__ al tablero __board__", + "act-addBoardMember": "agregado el mimbro __member__ al tablero __board__", + "act-archivedBoard": "El tablero __board__ se ha archivado", + "act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado", + "act-importBoard": "importado el tablero __board__", + "act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__", + "act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__", + "act-joinMember": "agregado el miembro __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-moveCard": "movida la tarjeta __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__", + "act-moveCardToOtherBoard": "movida la tarjeta __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeBoardMember": "eliminado el miembro __member__ del tablero __board__", + "act-restoredCard": "restaurada la tarjeta __card__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-unjoinMember": "eliminado el miembro __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Acciones", + "activities": "Actividades", + "activity": "Actividad", + "activity-added": "ha agregado %s a %s", + "activity-archived": "%s se ha archivado", + "activity-attached": "ha adjuntado %s a %s", + "activity-created": "ha creado %s", + "activity-customfield-created": "creó el campo personalizado %s", + "activity-excluded": "ha excluido %s de %s", + "activity-imported": "ha importado %s a %s desde %s", + "activity-imported-board": "ha importado %s desde %s", + "activity-joined": "se ha unido a %s", + "activity-moved": "ha movido %s de %s a %s", + "activity-on": "en %s", + "activity-removed": "ha eliminado %s de %s", + "activity-sent": "ha enviado %s a %s", + "activity-unjoined": "se ha desvinculado de %s", + "activity-subtask-added": "ha agregado la subtarea a %s", + "activity-checked-item": "marcado %s en la lista de comprobación %s de %s", + "activity-unchecked-item": "desmarcado %s en la lista de comprobación %s de %s", + "activity-checklist-added": "ha agregado una lista de comprobación a %s", + "activity-checklist-removed": "eliminada una lista de comprobación desde %s", + "activity-checklist-completed": "lista de comprobación completada %s de %s", + "activity-checklist-uncompleted": "no completado la lista de comprobación %s de %s", + "activity-checklist-item-added": "ha agregado el elemento de la lista de comprobación a «%s» en %s", + "activity-checklist-item-removed": "eliminado un elemento de la lista de comprobación desde «%s» en %s", + "add": "Agregar", + "activity-checked-item-card": "marcado %s en la lista de comprobación %s", + "activity-unchecked-item-card": "desmarcado %s en la lista de comprobación %s", + "activity-checklist-completed-card": "completada la lista de comprobación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "activity-checklist-uncompleted-card": "no completó la lista de comprobación %s", + "activity-editComment": "comentario editado", + "activity-deleteComment": "comentario eliminado", + "activity-receivedDate": "editada la fecha de recepción a %s de %s", + "activity-startDate": "editada la fecha de inicio a %s de %s", + "activity-dueDate": "editada la fecha de vencimiento a %s de %s", + "activity-endDate": "editada la fecha de finalización a %s de %s", + "add-attachment": "Agregar adjunto", + "add-board": "Agregar tablero", + "add-template": "Add Template", + "add-card": "Agregar una tarjeta", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Agregar un carril «swimlane»", + "add-subtask": "Agregar subtarea", + "add-checklist": "Agregar lista de comprobación", + "add-checklist-item": "Agregar un elemento a la lista de comprobación", + "add-cover": "Agregar portada", + "add-label": "Agregar una etiqueta", + "add-list": "Agregar una lista", + "add-members": "Agregar miembros", + "added": "Agregada el", + "addMemberPopup-title": "Miembros", + "admin": "Administrador", + "admin-desc": "Puede ver y editar tarjetas, eliminar miembros y cambiar la configuración del tablero.", + "admin-announcement": "Anuncio", + "admin-announcement-active": "Habilitar los anuncios en todo el sistema", + "admin-announcement-title": "Anuncio del administrador", + "all-boards": "Todos los tableros", + "and-n-other-card": "y __count__ tarjeta más", + "and-n-other-card_plural": "y otras __count__ tarjetas", + "apply": "Aplicar", + "app-is-offline": "Cargando, por favor espere. Actualizar la página causará pérdida de datos. Si la carga no funciona, por favor compruebe que el servidor no se ha detenido.", + "archive": "Archivar", + "archive-all": "Archivar todo", + "archive-board": "Archivar este tablero", + "archive-card": "Archivar esta tarjeta", + "archive-list": "Archivar esta lista", + "archive-swimlane": "Archivar este carril", + "archive-selection": "Archivar esta selección", + "archiveBoardPopup-title": "¿Archivar este tablero?", + "archived-items": "Archivo", + "archived-boards": "Tableros en el archivo", + "restore-board": "Restaurar el tablero", + "no-archived-boards": "No hay tableros en el archivo", + "archives": "Archivo", + "template": "Plantilla", + "templates": "Plantillas", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Asignar miembros", + "attached": "adjuntado", + "attachment": "Adjunto", + "attachment-delete-pop": "La eliminación de un fichero adjunto es permanente. Esta acción no puede deshacerse.", + "attachmentDeletePopup-title": "¿Eliminar el adjunto?", + "attachments": "Adjuntos", + "auto-watch": "Suscribirse automáticamente a los tableros cuando son creados", + "avatar-too-big": "El avatar es muy grande (520 KB máx.)", + "back": "Atrás", + "board-change-color": "Cambiar el color", + "board-nb-stars": "%s destacados", + "board-not-found": "Tablero no encontrado", + "board-private-info": "Este tablero será <strong>privado</strong>.", + "board-public-info": "Este tablero será <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Cambiar el fondo del tablero", + "boardChangeTitlePopup-title": "Renombrar el tablero", + "boardChangeVisibilityPopup-title": "Cambiar visibilidad", + "boardChangeWatchPopup-title": "Cambiar vigilancia", + "boardMenuPopup-title": "Configuración del tablero", + "boardChangeViewPopup-title": "Vista del tablero", + "boards": "Tableros", + "board-view": "Vista del tablero", + "board-view-cal": "Calendario", + "board-view-swimlanes": "Carriles", + "board-view-collapse": "Contraer", + "board-view-gantt": "Gantt", + "board-view-lists": "Listas", + "bucket-example": "Como «Cosas por hacer» por ejemplo", + "cancel": "Cancelar", + "card-archived": "Se archivó esta tarjeta", + "board-archived": "Se archivó este tablero", + "card-comments-title": "Esta tarjeta tiene %s comentarios.", + "card-delete-notice": "la eliminación es permanente. Perderá todas las acciones asociadas a esta tarjeta.", + "card-delete-pop": "Se eliminarán todas las acciones de la fuente de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.", + "card-delete-suggest-archive": "Puede mover una tarjeta al archivo para quitarla del tablero y preservar la actividad.", + "card-due": "Vence", + "card-due-on": "Vence el", + "card-spent": "Tiempo consumido", + "card-edit-attachments": "Editar los adjuntos", + "card-edit-custom-fields": "Editar los campos personalizados", + "card-edit-labels": "Editar las etiquetas", + "card-edit-members": "Editar los miembros", + "card-labels-title": "Cambia las etiquetas de la tarjeta.", + "card-members-title": "Agregar o eliminar miembros del tablero desde la tarjeta.", + "card-start": "Comienza", + "card-start-on": "Comienza el", + "cardAttachmentsPopup-title": "Adjuntar desde", + "cardCustomField-datePopup-title": "Cambiar la fecha", + "cardCustomFieldsPopup-title": "Editar los campos personalizados", + "cardStartVotingPopup-title": "Iniciar una votación", + "positiveVoteMembersPopup-title": "Favorables", + "negativeVoteMembersPopup-title": "Contrarios", + "card-edit-voting": "Editar votación", + "editVoteEndDatePopup-title": "Cambiar la fecha de finalización de la votación", + "allowNonBoardMembers": "Permitir todos los usuarios autentificados", + "vote-question": "Pregunta de votación", + "vote-public": "Mostrar quien voto que", + "vote-for-it": "por esto", + "vote-against": "contrarios", + "deleteVotePopup-title": "¿Borrar voto?", + "vote-delete-pop": "El borrado es permanente. Perderá todas las acciones asociadas con este voto.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "¿Eliminar la tarjeta?", + "cardDetailsActionsPopup-title": "Acciones de la tarjeta", + "cardLabelsPopup-title": "Etiquetas", + "cardMembersPopup-title": "Miembros", + "cardMorePopup-title": "Más", + "cardTemplatePopup-title": "Crear plantilla", + "cards": "Tarjetas", + "cards-count": "Tarjetas", + "cards-count-one": "Tarjeta", + "casSignIn": "Iniciar sesión con CAS", + "cardType-card": "Tarjeta", + "cardType-linkedCard": "Tarjeta enlazada", + "cardType-linkedBoard": "Tablero enlazado", + "change": "Cambiar", + "change-avatar": "Cambiar el avatar", + "change-password": "Cambiar la contraseña", + "change-permissions": "Cambiar los permisos", + "change-settings": "Cambiar la configuración", + "changeAvatarPopup-title": "Cambiar el avatar", + "changeLanguagePopup-title": "Cambiar el idioma", + "changePasswordPopup-title": "Cambiar la contraseña", + "changePermissionsPopup-title": "Cambiar los permisos", + "changeSettingsPopup-title": "Cambiar la configuración", + "subtasks": "Subtareas", + "checklists": "Listas de comprobación", + "click-to-star": "Haga clic para destacar este tablero.", + "click-to-unstar": "Haga clic para dejar de destacar este tablero.", + "clipboard": "el portapapeles o con arrastrar y soltar", + "close": "Cerrar", + "close-board": "Cerrar el tablero", + "close-board-pop": "Podrá restaurar el tablero haciendo clic en el botón «Archivo» de la cabecera de inicio.", + "close-card": "Close Card", + "color-black": "negro", + "color-blue": "azul", + "color-crimson": "carmesí", + "color-darkgreen": "verde oscuro", + "color-gold": "oro", + "color-gray": "gris", + "color-green": "verde", + "color-indigo": "añil", + "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "rosa claro", + "color-navy": "azul marino", + "color-orange": "naranja", + "color-paleturquoise": "turquesa", + "color-peachpuff": "melocotón", + "color-pink": "rosa", + "color-plum": "púrpura", + "color-purple": "violeta", + "color-red": "roja", + "color-saddlebrown": "marrón", + "color-silver": "plata", + "color-sky": "celeste", + "color-slateblue": "azul", + "color-white": "blanco", + "color-yellow": "amarillo", + "unset-color": "Desmarcar", + "comment": "Comentar", + "comment-placeholder": "Escribir comentario", + "comment-only": "Sólo comentarios", + "comment-only-desc": "Sólo puede comentar en las tarjetas.", + "no-comments": "No hay comentarios", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", + "worker": "Trabajador", + "worker-desc": "Sólo puede mover tarjetas, asignarse a la tarjeta y comentar.", + "computer": "Computadora", + "confirm-subtask-delete-dialog": "¿Seguro que desea eliminar la subtarea?", + "confirm-checklist-delete-dialog": "¿Seguro que desea eliminar la lista de comprobación?", + "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", + "linkCardPopup-title": "Enlazar tarjeta", + "searchElementPopup-title": "Buscar", + "copyCardPopup-title": "Copiar la tarjeta", + "copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de comprobación en varias tarjetas", + "copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", + "create": "Crear", + "createBoardPopup-title": "Crear tablero", + "chooseBoardSourcePopup-title": "Importar un tablero", + "createLabelPopup-title": "Crear etiqueta", + "createCustomField": "Crear un campo", + "createCustomFieldPopup-title": "Crear un campo", + "current": "actual", + "custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "custom-field-checkbox": "Casilla de verificación", + "custom-field-currency": "Divisa", + "custom-field-currency-option": "Código de divisa", + "custom-field-date": "Fecha", + "custom-field-dropdown": "Lista desplegable", + "custom-field-dropdown-none": "(ninguno)", + "custom-field-dropdown-options": "Opciones de la lista", + "custom-field-dropdown-options-placeholder": "Presione Entrar para agregar más opciones", + "custom-field-dropdown-unknown": "(desconocido)", + "custom-field-number": "Número", + "custom-field-text": "Texto", + "custom-fields": "Campos personalizados", + "date": "Fecha", + "decline": "Declinar", + "default-avatar": "Avatar por defecto", + "delete": "Eliminar", + "deleteCustomFieldPopup-title": "¿Eliminar el campo personalizado?", + "deleteLabelPopup-title": "¿Eliminar la etiqueta?", + "description": "Descripción", + "disambiguateMultiLabelPopup-title": "Desambiguar la acción de etiqueta", + "disambiguateMultiMemberPopup-title": "Desambiguar la acción de miembro", + "discard": "Descartar", + "done": "Hecho", + "download": "Descargar", + "edit": "Editar", + "edit-avatar": "Cambiar el avatar", + "edit-profile": "Editar el perfil", + "edit-wip-limit": "Cambiar el límite del trabajo en proceso", + "soft-wip-limit": "Límite del trabajo en proceso flexible", + "editCardStartDatePopup-title": "Cambiar la fecha de inicio", + "editCardDueDatePopup-title": "Cambiar la fecha de vencimiento", + "editCustomFieldPopup-title": "Editar el campo", + "editCardSpentTimePopup-title": "Cambiar el tiempo consumido", + "editLabelPopup-title": "Cambiar la etiqueta", + "editNotificationPopup-title": "Editar las notificaciones", + "editProfilePopup-title": "Editar el perfil", + "email": "Correo electrónico", + "email-enrollAccount-subject": "Cuenta creada en __siteName__", + "email-enrollAccount-text": "Hola __user__,\n\nPara empezar a utilizar el servicio, simplemente haga clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-fail": "Error al enviar el correo", + "email-fail-text": "Error al intentar enviar el correo", + "email-invalid": "Correo no válido", + "email-invite": "Invitar vía correo electrónico", + "email-invite-subject": "__inviter__ ha enviado una invitación", + "email-invite-text": "Estimado __user__,\n\n__inviter__ le invita a unirse al tablero «__board__» para colaborar.\n\nPor favor, haga clic en el siguiente enlace:\n\n__url__\n\nGracias.", + "email-resetPassword-subject": "Restablezca su contraseña en __siteName__", + "email-resetPassword-text": "Hola __user__,\n\nPara restablecer su contraseña, simplemente haga clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-sent": "Correo enviado", + "email-verifyEmail-subject": "Verifique su dirección de correo electrónico en __siteName__", + "email-verifyEmail-text": "Hola __user__,\n\nPara verificar el correo electrónico de su cuenta, simplemente haga clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "enable-wip-limit": "Habilitar el límite del trabajo en proceso", + "error-board-doesNotExist": "El tablero no existe", + "error-board-notAdmin": "Es necesario ser administrador de este tablero para hacer eso", + "error-board-notAMember": "Es necesario ser miembro de este tablero para hacer eso", + "error-json-malformed": "El texto no es un JSON válido", + "error-json-schema": "Sus datos JSON no incluyen la información apropiada en el formato correcto", + "error-csv-schema": "Su archivo CSV (valores separados por comas) o TSV (valores separados por tabulaciones) no incluye la información adecuada en el formato correcto", + "error-list-doesNotExist": "La lista no existe", + "error-user-doesNotExist": "El usuario no existe", + "error-user-notAllowSelf": "No se puede invitar a sí mismo", + "error-user-notCreated": "El usuario no ha sido creado", + "error-username-taken": "Este nombre de usuario ya está en uso", + "error-orgname-taken": "Este nombre de organización ya está en uso", + "error-teamname-taken": "Este nombre de equipo ya está en uso", + "error-email-taken": "Esta dirección de correo ya está en uso", + "export-board": "Exportar tablero", + "export-board-json": "Exportar tablero a JSON", + "export-board-csv": "Exportar tablero a CSV", + "export-board-tsv": "Exportar tablero a TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Exportar tablero a HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar tablero", + "exportCardPopup-title": "Export card", + "sort": "Ordenar", + "sort-desc": "Clic para ordenar lista", + "list-sort-by": "Ordenar la lista por:", + "list-label-modifiedAt": "Hora de último acceso", + "list-label-title": "Nombre de la lista", + "list-label-sort": "Su orden manual", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filtrar", + "filter-cards": "Filtrar tarjetas o listas", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filtrar listas por título", + "filter-clear": "Limpiar el filtro", + "filter-labels-label": "Filtrar por etiqueta", + "filter-no-label": "Sin etiqueta", + "filter-member-label": "Filtrar por miembro", + "filter-no-member": "Sin miembro", + "filter-assignee-label": "Filtrar por asignado", + "filter-no-assignee": "No asignado", + "filter-custom-fields-label": "Filtrar por campos personalizados", + "filter-no-custom-fields": "Sin campos personalizados", + "filter-show-archive": "Mostrar las listas archivadas", + "filter-hide-empty": "Ocultar las listas vacías", + "filter-on": "Filtrado activado", + "filter-on-desc": "Está filtrando las tarjetas en este tablero. Haga clic aquí para editar el filtro.", + "filter-to-selection": "Filtrar la selección", + "other-filters-label": "Otros filtros", + "advanced-filter-label": "Filtro avanzado", + "advanced-filter-description": "El filtrado avanzado permite escribir una cadena que contiene los siguientes operadores: == != <= >= && || ( ) Se utiliza un espacio como separador entre los operadores. Se pueden filtrar todos los campos personalizados escribiendo sus nombres y valores. Por ejemplo: Campo1 == Valor1. Nota: Si los campos o valores contienen espacios, deben encapsularse entre comillas simples. Por ejemplo: 'Campo 1' == 'Valor 1'. Para omitir los caracteres de control único (' \\/), se usa \\. Por ejemplo: Campo1 = I\\'m. También se pueden combinar múltiples condiciones. Por ejemplo: C1 == V1 || C1 == V2. Normalmente todos los operadores se interpretan de izquierda a derecha. Se puede cambiar el orden colocando paréntesis. Por ejemplo: C1 == V1 && ( C2 == V2 || C2 == V3 ). También se puede buscar en campos de texto usando expresiones regulares: C1 == /Tes.*/i", + "fullname": "Nombre completo", + "header-logo-title": "Volver a su página de tableros", + "hide-system-messages": "Ocultar las notificaciones de actividad", + "headerBarCreateBoardPopup-title": "Crear tablero", + "home": "Inicio", + "import": "Importar", + "impersonate-user": "Impersonar usuario", + "link": "Enlace", + "import-board": "importar un tablero", + "import-board-c": "Importar un tablero", + "import-board-title-trello": "Importar un tablero desde Trello", + "import-board-title-wekan": "Importar tablero desde una exportación previa", + "import-board-title-csv": "Importar tablero desde CSV/TSV", + "from-trello": "Desde Trello", + "from-wekan": "Desde exportación previa", + "from-csv": "Desde CSV/TSV", + "import-board-instruction-trello": "En su tablero de Trello, vaya a «Menú», luego «Más», «Imprimir y exportar», «Exportar en formato JSON», y copie el texto resultante.", + "import-board-instruction-csv": "Pegue en sus Valores separados por comas (CSV)/Valores separados por tabulaciones (TSV).", + "import-board-instruction-wekan": "En su tablero, vaya a «Menú», luego «Exportar tablero», y copie el texto en el archivo descargado.", + "import-board-instruction-about-errors": "Si se obtienen errores al importar el tablero, a veces la importación sigue funcionando, y el tablero está en la página de todos los tableros.", + "import-json-placeholder": "Pegue sus datos JSON válidos aquí", + "import-csv-placeholder": "Pegue sus datos CSV/TSV válidos aquí", + "import-map-members": "Asignar miembros", + "import-members-map": "Su tablero importado tiene algunos miembros. Por favor, asigne los miembros que desea importar a sus usuarios", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Revisión de la asignación de miembros", + "import-user-select": "Elija su usuario existente que desea utilizar como este miembro", + "importMapMembersAddPopup-title": "Seleccionar miembro", + "info": "Versión", + "initials": "Iniciales", + "invalid-date": "Fecha no válida", + "invalid-time": "Tiempo no válido", + "invalid-user": "Usuario no válido", + "joined": "se ha unido", + "just-invited": "Ha sido invitado a este tablero", + "keyboard-shortcuts": "Atajos de teclado", + "label-create": "Crear una etiqueta", + "label-default": "etiqueta %s (por defecto)", + "label-delete-pop": "Se eliminará esta etiqueta de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "labels": "Etiquetas", + "language": "Cambiar el idioma", + "last-admin-desc": "No se pueden cambiar los roles porque debe haber al menos un administrador.", + "leave-board": "Abandonar el tablero", + "leave-board-pop": "¿Seguro que desea abandonar __boardTitle__? Será desvinculado de todas las tarjetas en este tablero.", + "leaveBoardPopup-title": "¿Abandonar el tablero?", + "link-card": "Enlazar a esta tarjeta", + "list-archive-cards": "Archivar todas las tarjetas de esta lista", + "list-archive-cards-pop": "Esto eliminará todas las tarjetas de esta lista del tablero. Para ver las tarjetas en el archivo y traerlas de vuelta al tablero, haga clic en «Menú» > «Archivo».", + "list-move-cards": "Mover todas las tarjetas de esta lista", + "list-select-cards": "Seleccionar todas las tarjetas de esta lista", + "set-color-list": "Cambiar el color", + "listActionPopup-title": "Acciones de la lista", + "settingsUserPopup-title": "Configuración de usuario", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Acciones del carril «swimlane»", + "swimlaneAddPopup-title": "Agregar un carril «swimlane» debajo", + "listImportCardPopup-title": "Importar una tarjeta de Trello", + "listImportCardsTsvPopup-title": "Importar CSV/TSV", + "listMorePopup-title": "Más", + "link-list": "Enlazar a esta lista", + "list-delete-pop": "Todas las acciones serán eliminadas de la fuente de actividades y no se podrá recuperar la lista. Esta acción no puede deshacerse.", + "list-delete-suggest-archive": "Puede mover una lista a archivo para eliminarla del tablero y preservar la actividad.", + "lists": "Listas", + "swimlanes": "Carriles", + "log-out": "Finalizar la sesión", + "log-in": "Iniciar sesión", + "loginPopup-title": "Iniciar sesión", + "memberMenuPopup-title": "Configuración de miembros", + "members": "Miembros", + "menu": "Menú", + "move-selection": "Mover la selección", + "moveCardPopup-title": "Mover la tarjeta", + "moveCardToBottom-title": "Mover al final", + "moveCardToTop-title": "Mover al principio", + "moveSelectionPopup-title": "Mover la selección", + "multi-selection": "Selección múltiple", + "multi-selection-label": "Establecer etiqueta para la selección", + "multi-selection-member": "Establecer miembro para la selección", + "multi-selection-on": "Selección múltiple activada", + "muted": "Silenciado", + "muted-info": "Nunca se le notificará ningún cambio en este tablero", + "my-boards": "Mis tableros", + "name": "Nombre", + "no-archived-cards": "No hay tarjetas archivadas.", + "no-archived-lists": "No hay listas archivadas.", + "no-archived-swimlanes": "No hay carriles archivados.", + "no-results": "Sin resultados", + "normal": "Normal", + "normal-desc": "Puede ver y editar las tarjetas. No puede cambiar la configuración.", + "not-accepted-yet": "La invitación no ha sido aceptada aún", + "notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participas como creador o miembro", + "notify-watch": "Recibir actualizaciones de cualquier tablero, lista o tarjeta que esté vigilando", + "optional": "opcional", + "or": "o", + "page-maybe-private": "Esta página puede ser privada. Puede verla <a href='%s'>iniciando sesión</a>.", + "page-not-found": "Página no encontrada.", + "password": "Contraseña", + "paste-or-dragdrop": "pegar o arrastrar y soltar un fichero de imagen (sólo imagen)", + "participating": "Participando", + "preview": "Previsualizar", + "previewAttachedImagePopup-title": "Previsualizar", + "previewClipboardImagePopup-title": "Previsualizar", + "private": "Privado", + "private-desc": "Este tablero es privado. Sólo las personas agregadas al tablero pueden verlo y editarlo.", + "profile": "Perfil", + "public": "Público", + "public-desc": "Este tablero es público. Es visible para cualquiera a través del enlace, y se mostrará en los buscadores como Google. Sólo las personas agregadas al tablero pueden editarlo.", + "quick-access-description": "Destaca un tablero para añadir un acceso directo en esta barra.", + "remove-cover": "Eliminar portada", + "remove-from-board": "Desvincular del tablero", + "remove-label": "Eliminar la etiqueta", + "listDeletePopup-title": "¿Eliminar la lista?", + "remove-member": "Eliminar miembro", + "remove-member-from-card": "Eliminar de la tarjeta", + "remove-member-pop": "¿Eliminar a __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero y recibirá una notificación.", + "removeMemberPopup-title": "¿Eliminar miembro?", + "rename": "Renombrar", + "rename-board": "Renombrar el tablero", + "restore": "Restaurar", + "save": "Guardar", + "search": "Buscar", + "rules": "Reglas", + "search-cards": "Buscar entre los títulos, las descripciones de las tarjetas/listas y los campos personalizados en este tablero.", + "search-example": "Escriba el texto que busca y presione Entrar", + "select-color": "Seleccionar el color", + "select-board": "Seleccionar tablero", + "set-wip-limit-value": "Cambiar el límite para el número máximo de tareas en esta lista.", + "setWipLimitPopup-title": "Cambiar el límite del trabajo en proceso", + "shortcut-assign-self": "Asignarse a la tarjeta actual", + "shortcut-autocomplete-emoji": "Autocompletar emoji", + "shortcut-autocomplete-members": "Autocompletar miembros", + "shortcut-clear-filters": "Limpiar todos los filtros", + "shortcut-close-dialog": "Cerrar el cuadro de diálogo", + "shortcut-filter-my-cards": "Filtrar mis tarjetas", + "shortcut-show-shortcuts": "Mostrar esta lista de atajos", + "shortcut-toggle-filterbar": "Alternar la barra lateral del filtro", + "shortcut-toggle-searchbar": "Alternar la barra lateral de búsqueda", + "shortcut-toggle-sidebar": "Alternar la barra lateral del tablero", + "show-cards-minimum-count": "Mostrar recuento de tarjetas si la lista contiene más de", + "sidebar-open": "Abrir la barra lateral", + "sidebar-close": "Cerrar la barra lateral", + "signupPopup-title": "Crear una cuenta", + "star-board-title": "Haga clic para destacar este tablero. Se mostrará en la parte superior de su lista de tableros.", + "starred-boards": "Tableros destacados", + "starred-boards-description": "Los tableros destacados se mostrarán en la parte superior de su lista de tableros.", + "subscribe": "Suscribirse", + "team": "Equipo", + "this-board": "este tablero", + "this-card": "esta tarjeta", + "spent-time-hours": "Tiempo consumido (horas)", + "overtime-hours": "Tiempo excesivo (horas)", + "overtime": "Tiempo excesivo", + "has-overtime-cards": "Hay tarjetas con el tiempo excedido", + "has-spenttime-cards": "Se ha excedido el tiempo de las tarjetas", + "time": "Hora", + "title": "Título", + "tracking": "Siguiendo", + "tracking-info": "Será notificado de cualquier cambio en las tarjetas en las que participa como creador o miembro.", + "type": "Tipo", + "unassign-member": "Desasignar miembro", + "unsaved-description": "Tiene una descripción sin guardar.", + "unwatch": "Dejar de vigilar", + "upload": "Cargar", + "upload-avatar": "Cargar un avatar", + "uploaded-avatar": "Avatar cargado", + "custom-top-left-corner-logo-image-url": "URL de imagen del logo personalizado de la esquina superior izquierda", + "custom-top-left-corner-logo-link-url": "URL de enlace del logo personalizado de la esquina superior izquierda", + "custom-top-left-corner-logo-height": "Altura del logo de la esquina superior izquierda. Por defecto: 27", + "custom-login-logo-image-url": "URL de imagen del logo personalizado de inicio de sesión", + "custom-login-logo-link-url": "URL de enlace del logo personalizado de inicio de sesión", + "text-below-custom-login-logo": "Texto debajo del logo personalizado de inicio de sesión", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Nombre de usuario", + "import-usernames": "Import Usernames", + "view-it": "Verla", + "warn-list-archived": "advertencia: esta tarjeta está en una lista en el archivo", + "watch": "Vigilar", + "watching": "Vigilando", + "watching-info": "Será notificado de cualquier cambio en este tablero", + "welcome-board": "Tablero de bienvenida", + "welcome-swimlane": "Hito 1", + "welcome-list1": "Básicos", + "welcome-list2": "Avanzados", + "card-templates-swimlane": "Plantilla de tarjeta", + "list-templates-swimlane": "Listar plantillas", + "board-templates-swimlane": "Plantilla de tablero", + "what-to-do": "¿Qué desea hacer?", + "wipLimitErrorPopup-title": "El límite del trabajo en proceso no es válido.", + "wipLimitErrorPopup-dialog-pt1": "El número de tareas en esta lista es mayor que el límite del trabajo en proceso que ha definido.", + "wipLimitErrorPopup-dialog-pt2": "Por favor, mueva algunas tareas de esta lista, o establezca un límite de trabajo en proceso más alto.", + "admin-panel": "Panel de administración", + "settings": "Configuración", + "people": "Personas", + "registration": "Registro", + "disable-self-registration": "Deshabilitar autoregistro", + "invite": "Invitar", + "invite-people": "Invitar a personas", + "to-boards": "A el(los) tablero(s)", + "email-addresses": "Direcciones de correo electrónico", + "smtp-host-description": "La dirección del servidor SMTP que maneja sus correos electrónicos.", + "smtp-port-description": "Puerto usado por el servidor SMTP para mandar correos", + "smtp-tls-description": "Habilitar el soporte TLS para el servidor SMTP", + "smtp-host": "Servidor SMTP", + "smtp-port": "Puerto SMTP", + "smtp-username": "Nombre de usuario", + "smtp-password": "Contraseña", + "smtp-tls": "Soporte TLS", + "send-from": "Desde", + "send-smtp-test": "Enviar un correo de prueba a usted mismo", + "invitation-code": "Código de Invitación", + "email-invite-register-subject": "__inviter__ le envió una invitación", + "email-invite-register-text": "Estimado __user__,\n\n__inviter__ le invita a participar en un tablero Kanban para colaborar.\n\nPor favor, siga el siguiente enlace:\n__url__\n\nY su código de invitación es: __icode__\n\nGracias.", + "email-smtp-test-subject": "Correo de prueba SMTP", + "email-smtp-test-text": "El correo se ha enviado correctamente", + "error-invitation-code-not-exist": "El código de invitación no existe", + "error-notAuthorized": "No está autorizado a ver esta página.", + "webhook-title": "Nombre del Webhook", + "webhook-token": "Token (opcional para la autenticación)", + "outgoing-webhooks": "Webhooks salientes", + "bidirectional-webhooks": "Webhooks de doble sentido", + "outgoingWebhooksPopup-title": "Webhooks salientes", + "boardCardTitlePopup-title": "Filtro de títulos de tarjeta", + "disable-webhook": "Deshabilitar este Webhook", + "global-webhook": "Webhooks globales", + "new-outgoing-webhook": "Nuevo webhook saliente", + "no-name": "(Desconocido)", + "Node_version": "Versión de Node", + "Meteor_version": "Versión de Meteor", + "MongoDB_version": "Versión de MongoDB", + "MongoDB_storage_engine": "Motor de almacenamiento de MongoDB", + "MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado", + "OS_Arch": "Arquitectura del sistema", + "OS_Cpus": "Número de CPUs del sistema", + "OS_Freemem": "Memoria libre del sistema", + "OS_Loadavg": "Carga media del sistema", + "OS_Platform": "Plataforma del sistema", + "OS_Release": "Publicación del sistema", + "OS_Totalmem": "Memoria total del sistema", + "OS_Type": "Tipo de sistema", + "OS_Uptime": "Tiempo activo del sistema", + "days": "días", + "hours": "horas", + "minutes": "minutos", + "seconds": "segundos", + "show-field-on-card": "Mostrar este campo en la tarjeta", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Mostrar etiquetas de campos en la minitarjeta.", + "yes": "Sí", + "no": "No", + "accounts": "Cuentas", + "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", + "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", + "createdAt": "Creado en", + "modifiedAt": "Modified at", + "verified": "Verificado", + "active": "Activo", + "card-received": "Recibido", + "card-received-on": "Recibido el", + "card-end": "Finalizado", + "card-end-on": "Finalizado el", + "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", + "editCardEndDatePopup-title": "Cambiar la fecha de finalización", + "setCardColorPopup-title": "Cambiar el color", + "setCardActionsColorPopup-title": "Elegir un color", + "setSwimlaneColorPopup-title": "Elegir un color", + "setListColorPopup-title": "Elegir un color", + "assigned-by": "Asignado por", + "requested-by": "Solicitado por", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", + "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", + "boardDeletePopup-title": "¿Eliminar el tablero?", + "delete-board": "Eliminar el tablero", + "default-subtasks-board": "Subtareas para el tablero __board__", + "default": "Por defecto", + "queue": "Cola", + "subtask-settings": "Configuración de subtareas", + "card-settings": "Configuración de tarjeta", + "boardSubtaskSettingsPopup-title": "Configuración de subtareas del tablero", + "boardCardSettingsPopup-title": "Configuración de tarjeta", + "deposit-subtasks-board": "Depositar subtareas en este tablero:", + "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:", + "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:", + "prefix-with-full-path": "Prefijo con ruta completa", + "prefix-with-parent": "Prefijo con el padre", + "subtext-with-full-path": "Subtexto con ruta completa", + "subtext-with-parent": "Subtexto con el padre", + "change-card-parent": "Cambiar la tarjeta padre", + "parent-card": "Tarjeta padre", + "source-board": "Tablero de origen", + "no-parent": "No mostrar la tarjeta padre", + "activity-added-label": "agregada etiqueta «%s» a %s", + "activity-removed-label": "eliminada etiqueta '%s' desde %s", + "activity-delete-attach": "eliminado un adjunto desde %s", + "activity-added-label-card": "agregada etiqueta «%s»", + "activity-removed-label-card": "eliminada etiqueta '%s'", + "activity-delete-attach-card": "eliminado un adjunto", + "activity-set-customfield": "Cambiar el campo personalizado '%s' a '%s' en %s", + "activity-unset-customfield": "Desmarcar el campo personalizado '%s' en %s", + "r-rule": "Regla", + "r-add-trigger": "Agregar desencadenador", + "r-add-action": "Agregar acción", + "r-board-rules": "Reglas del tablero", + "r-add-rule": "Agregar regla", + "r-view-rule": "Ver regla", + "r-delete-rule": "Eliminar regla", + "r-new-rule-name": "Nuevo título de regla", + "r-no-rules": "No hay reglas", + "r-trigger": "Desencadenador", + "r-action": "Acción", + "r-when-a-card": "Cuando una tarjeta", + "r-is": "es", + "r-is-moved": "es movida", + "r-added-to": "Agregado a", + "r-removed-from": "eliminado de", + "r-the-board": "el tablero", + "r-list": "la lista", + "list": "Lista", + "set-filter": "Filtrar", + "r-moved-to": "Movido a", + "r-moved-from": "Movido desde", + "r-archived": "Se archivó", + "r-unarchived": "Restaurado del archivo", + "r-a-card": "una tarjeta", + "r-when-a-label-is": "Cuando una etiqueta es", + "r-when-the-label": "Cuando la etiqueta es", + "r-list-name": "Nombre de lista", + "r-when-a-member": "Cuando un miembro es", + "r-when-the-member": "Cuando el miembro", + "r-name": "nombre", + "r-when-a-attach": "Cuando un adjunto", + "r-when-a-checklist": "Cuando una lista de comprobación es", + "r-when-the-checklist": "Cuando la lista de comprobación", + "r-completed": "Completada", + "r-made-incomplete": "Hecha incompleta", + "r-when-a-item": "Cuando un elemento de la lista de comprobación es", + "r-when-the-item": "Cuando el elemento de la lista de comprobación es", + "r-checked": "Marcado", + "r-unchecked": "Desmarcado", + "r-move-card-to": "Mover la tarjeta", + "r-top-of": "Arriba de", + "r-bottom-of": "Abajo de", + "r-its-list": "su lista", + "r-archive": "Archivar", + "r-unarchive": "Restaurar del archivo", + "r-card": "la tarjeta", + "r-add": "Agregar", + "r-remove": "Eliminar", + "r-label": "etiqueta", + "r-member": "miembro", + "r-remove-all": "Eliminar todos los miembros de la tarjeta", + "r-set-color": "Cambiar el color a", + "r-checklist": "lista de comprobación", + "r-check-all": "Marcar todo", + "r-uncheck-all": "Desmarcar todo", + "r-items-check": "elementos de la lista de comprobación", + "r-check": "Marcar", + "r-uncheck": "Desmarcar", + "r-item": "elemento", + "r-of-checklist": "de la lista de comprobación", + "r-send-email": "Enviar un correo electrónico", + "r-to": "a", + "r-of": "de", + "r-subject": "asunto", + "r-rule-details": "Detalle de la regla", + "r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista", + "r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista", + "r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista", + "r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista", + "r-d-send-email": "Enviar correo electrónico", + "r-d-send-email-to": "a", + "r-d-send-email-subject": "asunto", + "r-d-send-email-message": "mensaje", + "r-d-archive": "Archivar la tarjeta", + "r-d-unarchive": "Restaurar tarjeta del archivo", + "r-d-add-label": "Agregar etiqueta", + "r-d-remove-label": "Eliminar etiqueta", + "r-create-card": "Crear nueva tarjeta", + "r-in-list": "en la lista", + "r-in-swimlane": "en el carril", + "r-d-add-member": "Agregar miembro", + "r-d-remove-member": "Eliminar miembro", + "r-d-remove-all-member": "Eliminar todos los miembros", + "r-d-check-all": "Marcar todos los elementos de una lista", + "r-d-uncheck-all": "Desmarcar todos los elementos de una lista", + "r-d-check-one": "Marcar elemento", + "r-d-uncheck-one": "Desmarcar elemento", + "r-d-check-of-list": "de la lista de comprobación", + "r-d-add-checklist": "Agregar lista de comprobación", + "r-d-remove-checklist": "Eliminar lista de comprobación", + "r-by": "por", + "r-add-checklist": "Agregar lista de comprobación", + "r-with-items": "con elementos", + "r-items-list": "elemento1,elemento2,elemento3", + "r-add-swimlane": "Agregar el carril", + "r-swimlane-name": "nombre del carril", + "r-board-note": "Nota: deje un campo vacío para que coincida con todos los valores posibles", + "r-checklist-note": "Nota: los elementos de la lista de comprobación deben escribirse como valores separados por comas.", + "r-when-a-card-is-moved": "Cuando una tarjeta es movida a otra lista", + "r-set": "Cambiar", + "r-update": "Actualizar", + "r-datefield": "campo de fecha", + "r-df-start-at": "comienza", + "r-df-due-at": "vencimiento", + "r-df-end-at": "finalizado", + "r-df-received-at": "recibido", + "r-to-current-datetime": "a la fecha/hora actual", + "r-remove-value-from": "Eliminar el valor de", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Método de autenticación", + "authentication-type": "Tipo de autenticación", + "custom-product-name": "Nombre de producto personalizado", + "layout": "Diseño", + "hide-logo": "Ocultar el logo", + "add-custom-html-after-body-start": "Agrega HTML personalizado después de <body>", + "add-custom-html-before-body-end": "Agrega HTML personalizado después de </body>", + "error-undefined": "Algo no está bien", + "error-ldap-login": "Ocurrió un error al intentar acceder", + "display-authentication-method": "Mostrar el método de autenticación", + "default-authentication-method": "Método de autenticación por defecto", + "duplicate-board": "Duplicar tablero", + "org-number": "El número de organizaciones es:", + "team-number": "El número de equipos es:", + "people-number": "El número de personas es:", + "swimlaneDeletePopup-title": "¿Eliminar el carril «swimlane»?", + "swimlane-delete-pop": "Todas las acciones serán eliminadas de la fuente de actividades y no se podrá recuperar el carril «swimlane». Esta acción no puede deshacerse.", + "restore-all": "Restaurar todas", + "delete-all": "Borrar todas", + "loading": "Cargando. Por favor, espere.", + "previous_as": "el último tiempo fue", + "act-a-dueAt": "cambiada la hora de vencimiento a \nCuándo: __timeValue__\nDónde: __card__\n el vencimiento anterior fue __timeOldValue__", + "act-a-endAt": "cambiada la hora de finalización a __timeValue__ Fecha anterior: (__timeOldValue__)", + "act-a-startAt": "cambiada la hora de inicio a __timeValue__ de (__timeOldValue__)", + "act-a-receivedAt": "cambiada la fecha de recepción a __timeValue__ Fecha anterior: (__timeOldValue__)", + "a-dueAt": "cambiada la hora de vencimiento a", + "a-endAt": "cambiada la hora de finalización a", + "a-startAt": "cambiada la hora de inicio a", + "a-receivedAt": "cambiada la hora de recepción a", + "almostdue": "está próxima la hora de vencimiento actual %s", + "pastdue": "se sobrepasó la hora de vencimiento actual %s", + "duenow": "la hora de vencimiento actual %s es hoy", + "act-newDue": "__list__/__card__ tiene una 1ra notificación de vencimiento [__board__]", + "act-withDue": "__list__/__card__ notificaciones de vencimiento [__board__]", + "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", + "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", + "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", + "act-atUserComment": "Se le mencionó en [__board__] __list__/__card__", + "delete-user-confirm-popup": "¿Seguro que desea eliminar esta cuenta? Esta acción no puede deshacerse.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta", + "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta", + "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio", + "assignee": "Asignado", + "cardAssigneesPopup-title": "Asignado", + "addmore-detail": "Agregar una descripción detallada", + "show-on-card": "Mostrar en la tarjeta", + "new": "Nuevo", + "editOrgPopup-title": "Editar organización", + "newOrgPopup-title": "Nueva organización", + "editTeamPopup-title": "Editar equipo", + "newTeamPopup-title": "Nuevo equipo", + "editUserPopup-title": "Editar el usuario", + "newUserPopup-title": "Nuevo usuario", + "notifications": "Notificaciones", + "view-all": "Ver todo", + "filter-by-unread": "Filtrar por no leído", + "mark-all-as-read": "Marcar todo como leido", + "remove-all-read": "Eliminar todos los leídos", + "allow-rename": "Permitir renombrar", + "allowRenamePopup-title": "Permitir renombrar", + "start-day-of-week": "Establecer el día de inicio de la semana", + "monday": "lunes", + "tuesday": "martes", + "wednesday": "miércoles", + "thursday": "jueves", + "friday": "viernes", + "saturday": "sábado", + "sunday": "domingo", + "status": "Estado", + "swimlane": "Carril", + "owner": "Propietario", + "last-modified-at": "Última modificación", + "last-activity": "Última actividad", + "voting": "Votar", + "archived": "Archivado", + "delete-linked-card-before-this-card": "No puede borrar esta tarjeta antes de borrar la tarjeta enlazada que tiene", + "delete-linked-cards-before-this-list": "No puede borrar esta lista antes de borrar las tarjetas enlazadas que apuntan a tarjetas en esta lista", + "hide-checked-items": "Ocultar elementos marcados", + "task": "Tarea", + "create-task": "Crear tarea", + "ok": "OK", + "organizations": "Organizaciones", + "teams": "Equipos", + "displayName": "Nombre para mostrar", + "shortName": "Nombre corto", + "website": "Sitio web", + "person": "Persona", + "my-cards": "Mis tarjetas", + "card": "Tarjeta", + "board": "Tablero", + "context-separator": "/", + "myCardsSortChange-title": "Orden de mis tarjetas", + "myCardsSortChangePopup-title": "Orden de mis tarjetas", + "myCardsSortChange-choice-board": "Por tablero", + "myCardsSortChange-choice-dueat": "Por fecha de vencimiento", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "Todos los usuarios", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "Lista «%s» no encontrada.", + "label-not-found": "Etiqueta «%s» no encontrada.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Nombre de usuario «%s» no encontrado.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "tablero", + "operator-board-abbrev": "t", + "operator-swimlane": "carril", + "operator-swimlane-abbrev": "c", + "operator-list": "la lista", + "operator-list-abbrev": "l", + "operator-label": "etiqueta", + "operator-label-abbrev": "#", + "operator-user": "usuario", + "operator-user-abbrev": "@", + "operator-member": "miembro", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "estado", + "operator-due": "vencimiento", + "operator-created": "creado", + "operator-modified": "modificado", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "año", + "predicate-due": "vencimiento", + "predicate-modified": "modificado", + "predicate-created": "creado", + "predicate-attachment": "attachment", + "predicate-description": "descripción", + "predicate-checklist": "lista de comprobación", + "predicate-start": "comienza", + "predicate-end": "finalizado", + "predicate-assignee": "assignee", + "predicate-member": "miembro", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s no es un operador", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notas", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Enlaces", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/es-PY.i18n.json b/i18n/es-PY.i18n.json new file mode 100644 index 000000000..f596968c0 --- /dev/null +++ b/i18n/es-PY.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Accept", + "act-activity-notify": "Activity Notification", + "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "created board __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Actions", + "activities": "Activities", + "activity": "Activity", + "activity-added": "added %s to %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/es.i18n.json b/i18n/es.i18n.json index b788693ce..558489a8e 100644 --- a/i18n/es.i18n.json +++ b/i18n/es.i18n.json @@ -1,769 +1,1062 @@ { - "accept": "Aceptar", - "act-activity-notify": "Notificación de actividad", - "act-addAttachment": "añadido el adjunto __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-deleteAttachment": "eliminado el adjunto __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addSubtask": "añadida la subtarea __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addedLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-removeLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-removedLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addChecklist": "añadida la lista de verificación __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addChecklistItem": "añadido el elemento __checklistItem__ a la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-removeChecklist": "eliminada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-removeChecklistItem": "eliminado el elemento __checklistItem__ de la lista de verificación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-checkedItem": "marcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-uncheckedItem": "desmarcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-completeChecklist": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-uncompleteChecklist": "no completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-createBoard": "creó el tablero __board__", - "act-createSwimlane": "creó el carril de flujo __swimlane__ en el tablero __board__", - "act-createCard": "creada la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-createCustomField": " creado el campo personalizado __customField__ en el tablero __board__", - "act-deleteCustomField": "eliminado el campo personalizado __customField__ del tablero __board__", - "act-setCustomField": "editado el campo personalizado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-createList": "añadida la lista __list__ al tablero __board__", - "act-addBoardMember": "añadido el mimbro __member__ al tablero __board__", - "act-archivedBoard": "El tablero __board__ se ha archivado", - "act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", - "act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", - "act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado", - "act-importBoard": "importado el tablero __board__", - "act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__", - "act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__", - "act-joinMember": "añadido el miembro __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-moveCard": "movida la tarjeta __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__", - "act-moveCardToOtherBoard": "movida la tarjeta __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__", - "act-removeBoardMember": "eliminado el miembro __member__ del tablero __board__", - "act-restoredCard": "restaurada la tarjeta __card__ a la lista __list__ del carril __swimlane__ del tablero __board__", - "act-unjoinMember": "eliminado el miembro __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Acciones", - "activities": "Actividades", - "activity": "Actividad", - "activity-added": "ha añadido %s a %s", - "activity-archived": "%s se ha archivado", - "activity-attached": "ha adjuntado %s a %s", - "activity-created": "ha creado %s", - "activity-customfield-created": "creó el campo personalizado %s", - "activity-excluded": "ha excluido %s de %s", - "activity-imported": "ha importado %s a %s desde %s", - "activity-imported-board": "ha importado %s desde %s", - "activity-joined": "se ha unido a %s", - "activity-moved": "ha movido %s de %s a %s", - "activity-on": "en %s", - "activity-removed": "ha eliminado %s de %s", - "activity-sent": "ha enviado %s a %s", - "activity-unjoined": "se ha desvinculado de %s", - "activity-subtask-added": "ha añadido la subtarea a %s", - "activity-checked-item": "marcado %s en la lista de verificación %s de %s", - "activity-unchecked-item": "desmarcado %s en lista %s de %s", - "activity-checklist-added": "ha añadido una lista de verificación a %s", - "activity-checklist-removed": "eliminada una lista de verificación desde %s", - "activity-checklist-completed": "lista de verificación completada %s de %s", - "activity-checklist-uncompleted": "no completado la lista %s de %s", - "activity-checklist-item-added": "ha añadido el elemento de la lista de verificación a '%s' en %s", - "activity-checklist-item-removed": "eliminado un elemento de la lista de verificación desde '%s' en %s", - "add": "Añadir", - "activity-checked-item-card": "marcado %s en la lista de verificación %s", - "activity-unchecked-item-card": "desmarcado %s en la lista de verificación %s", - "activity-checklist-completed-card": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", - "activity-checklist-uncompleted-card": "no completó la lista de verificación %s", - "activity-editComment": "comentario editado", - "activity-deleteComment": "comentario eliminado", - "add-attachment": "Añadir adjunto", - "add-board": "Añadir tablero", - "add-card": "Añadir una tarjeta", - "add-swimlane": "Añadir un carril de flujo", - "add-subtask": "Añadir subtarea", - "add-checklist": "Añadir una lista de verificación", - "add-checklist-item": "Añadir un elemento a la lista de verificación", - "add-cover": "Añadir portada", - "add-label": "Añadir una etiqueta", - "add-list": "Añadir una lista", - "add-members": "Añadir miembros", - "added": "Añadida el", - "addMemberPopup-title": "Miembros", - "admin": "Administrador", - "admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero", - "admin-announcement": "Aviso", - "admin-announcement-active": "Activar el aviso para todo el sistema", - "admin-announcement-title": "Aviso del administrador", - "all-boards": "Tableros", - "and-n-other-card": "y __count__ tarjeta más", - "and-n-other-card_plural": "y otras __count__ tarjetas", - "apply": "Aplicar", - "app-is-offline": "Cargando, espera por favor. Refrescar esta página causará pérdida de datos. Si la carga no funciona, por favor comprueba que el servidor no se ha parado.", - "archive": "Archivar", - "archive-all": "Archivar todo", - "archive-board": "Archivar este tablero", - "archive-card": "Archivar esta tarjeta", - "archive-list": "Archivar esta lista", - "archive-swimlane": "Archivar este carril", - "archive-selection": "Archivar esta selección", - "archiveBoardPopup-title": "¿Archivar este tablero?", - "archived-items": "Archivo", - "archived-boards": "Tableros en el Archivo", - "restore-board": "Restaurar el tablero", - "no-archived-boards": "No hay Tableros en el Archivo", - "archives": "Archivo", - "template": "Plantilla", - "templates": "Plantillas", - "assign-member": "Asignar miembros", - "attached": "adjuntado", - "attachment": "Adjunto", - "attachment-delete-pop": "La eliminación de un fichero adjunto es permanente. Esta acción no puede deshacerse.", - "attachmentDeletePopup-title": "¿Eliminar el adjunto?", - "attachments": "Adjuntos", - "auto-watch": "Suscribirse automáticamente a los tableros cuando son creados", - "avatar-too-big": "El avatar es muy grande (70KB máx.)", - "back": "Atrás", - "board-change-color": "Cambiar el color", - "board-nb-stars": "%s destacados", - "board-not-found": "Tablero no encontrado", - "board-private-info": "Este tablero será <strong>privado</strong>.", - "board-public-info": "Este tablero será <strong>público</strong>.", - "boardChangeColorPopup-title": "Cambiar el fondo del tablero", - "boardChangeTitlePopup-title": "Renombrar el tablero", - "boardChangeVisibilityPopup-title": "Cambiar visibilidad", - "boardChangeWatchPopup-title": "Cambiar vigilancia", - "boardMenuPopup-title": "Preferencias del tablero", - "boardChangeViewPopup-title": "Vista del tablero", - "boards": "Tableros", - "board-view": "Vista del tablero", - "board-view-cal": "Calendario", - "board-view-swimlanes": "Carriles", - "board-view-collapse": "Contraer", - "board-view-lists": "Listas", - "bucket-example": "Como “Cosas por hacer” por ejemplo", - "cancel": "Cancelar", - "card-archived": "Se archivó esta tarjeta", - "board-archived": "Se archivó este tablero", - "card-comments-title": "Esta tarjeta tiene %s comentarios.", - "card-delete-notice": "la eliminación es permanente. Perderás todas las acciones asociadas a esta tarjeta.", - "card-delete-pop": "Se eliminarán todas las acciones del historial de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.", - "card-delete-suggest-archive": "Puedes mover una tarjeta al Archivo para quitarla del tablero y preservar la actividad.", - "card-due": "Vence", - "card-due-on": "Vence el", - "card-spent": "Tiempo consumido", - "card-edit-attachments": "Editar los adjuntos", - "card-edit-custom-fields": "Editar los campos personalizados", - "card-edit-labels": "Editar las etiquetas", - "card-edit-members": "Editar los miembros", - "card-labels-title": "Cambia las etiquetas de la tarjeta", - "card-members-title": "Añadir o eliminar miembros del tablero desde la tarjeta.", - "card-start": "Comienza", - "card-start-on": "Comienza el", - "cardAttachmentsPopup-title": "Adjuntar desde", - "cardCustomField-datePopup-title": "Cambiar la fecha", - "cardCustomFieldsPopup-title": "Editar los campos personalizados", - "cardDeletePopup-title": "¿Eliminar la tarjeta?", - "cardDetailsActionsPopup-title": "Acciones de la tarjeta", - "cardLabelsPopup-title": "Etiquetas", - "cardMembersPopup-title": "Miembros", - "cardMorePopup-title": "Más", - "cardTemplatePopup-title": "Crear plantilla", - "cards": "Tarjetas", - "cards-count": "Tarjetas", - "casSignIn": "Iniciar sesión con CAS", - "cardType-card": "Tarjeta", - "cardType-linkedCard": "Tarjeta enlazada", - "cardType-linkedBoard": "Tablero enlazado", - "change": "Cambiar", - "change-avatar": "Cambiar el avatar", - "change-password": "Cambiar la contraseña", - "change-permissions": "Cambiar los permisos", - "change-settings": "Cambiar las preferencias", - "changeAvatarPopup-title": "Cambiar el avatar", - "changeLanguagePopup-title": "Cambiar el idioma", - "changePasswordPopup-title": "Cambiar la contraseña", - "changePermissionsPopup-title": "Cambiar los permisos", - "changeSettingsPopup-title": "Cambiar las preferencias", - "subtasks": "Subtareas", - "checklists": "Lista de verificación", - "click-to-star": "Haz clic para destacar este tablero.", - "click-to-unstar": "Haz clic para dejar de destacar este tablero.", - "clipboard": "el portapapeles o con arrastrar y soltar", - "close": "Cerrar", - "close-board": "Cerrar el tablero", - "close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.", - "color-black": "negra", - "color-blue": "azul", - "color-crimson": "carmesí", - "color-darkgreen": "verde oscuro", - "color-gold": "oro", - "color-gray": "gris", - "color-green": "verde", - "color-indigo": "añil", - "color-lime": "lima", - "color-magenta": "magenta", - "color-mistyrose": "rosa claro", - "color-navy": "azul marino", - "color-orange": "naranja", - "color-paleturquoise": "turquesa", - "color-peachpuff": "melocotón", - "color-pink": "rosa", - "color-plum": "púrpura", - "color-purple": "violeta", - "color-red": "roja", - "color-saddlebrown": "marrón", - "color-silver": "plata", - "color-sky": "celeste", - "color-slateblue": "azul", - "color-white": "blanco", - "color-yellow": "amarilla", - "unset-color": "Desmarcar", - "comment": "Comentar", - "comment-placeholder": "Escribir comentario", - "comment-only": "Sólo comentarios", - "comment-only-desc": "Solo puedes comentar en las tarjetas.", - "no-comments": "No hay comentarios", - "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", - "worker": "Trabajador", - "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.", - "computer": "el ordenador", - "confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?", - "confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?", - "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", - "linkCardPopup-title": "Enlazar tarjeta", - "searchElementPopup-title": "Buscar", - "copyCardPopup-title": "Copiar la tarjeta", - "copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de verificación en varias tarjetas", - "copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", - "create": "Crear", - "createBoardPopup-title": "Crear tablero", - "chooseBoardSourcePopup-title": "Importar un tablero", - "createLabelPopup-title": "Crear etiqueta", - "createCustomField": "Crear un campo", - "createCustomFieldPopup-title": "Crear un campo", - "current": "actual", - "custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", - "custom-field-checkbox": "Casilla de verificación", - "custom-field-date": "Fecha", - "custom-field-dropdown": "Lista desplegable", - "custom-field-dropdown-none": "(nada)", - "custom-field-dropdown-options": "Opciones de la lista", - "custom-field-dropdown-options-placeholder": "Pulsa Intro para añadir más opciones", - "custom-field-dropdown-unknown": "(desconocido)", - "custom-field-number": "Número", - "custom-field-text": "Texto", - "custom-fields": "Campos personalizados", - "date": "Fecha", - "decline": "Declinar", - "default-avatar": "Avatar por defecto", - "delete": "Eliminar", - "deleteCustomFieldPopup-title": "¿Eliminar el campo personalizado?", - "deleteLabelPopup-title": "¿Eliminar la etiqueta?", - "description": "Descripción", - "disambiguateMultiLabelPopup-title": "Desambiguar la acción de etiqueta", - "disambiguateMultiMemberPopup-title": "Desambiguar la acción de miembro", - "discard": "Descartarla", - "done": "Hecho", - "download": "Descargar", - "edit": "Editar", - "edit-avatar": "Cambiar el avatar", - "edit-profile": "Editar el perfil", - "edit-wip-limit": "Cambiar el límite del trabajo en proceso", - "soft-wip-limit": "Límite del trabajo en proceso flexible", - "editCardStartDatePopup-title": "Cambiar la fecha de comienzo", - "editCardDueDatePopup-title": "Cambiar la fecha de vencimiento", - "editCustomFieldPopup-title": "Editar el campo", - "editCardSpentTimePopup-title": "Cambiar el tiempo consumido", - "editLabelPopup-title": "Cambiar la etiqueta", - "editNotificationPopup-title": "Editar las notificaciones", - "editProfilePopup-title": "Editar el perfil", - "email": "Correo electrónico", - "email-enrollAccount-subject": "Cuenta creada en __siteName__", - "email-enrollAccount-text": "Hola __user__,\n\nPara empezar a utilizar el servicio, simplemente haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", - "email-fail": "Error al enviar el correo", - "email-fail-text": "Error al intentar enviar el correo", - "email-invalid": "Correo no válido", - "email-invite": "Invitar vía correo electrónico", - "email-invite-subject": "__inviter__ ha enviado una invitación", - "email-invite-text": "Estimado __user__,\n\n__inviter__ te invita a unirte al tablero '__board__' para colaborar.\n\nPor favor, haz clic en el siguiente enlace:\n\n__url__\n\nGracias.", - "email-resetPassword-subject": "Restablecer tu contraseña en __siteName__", - "email-resetPassword-text": "Hola __user__,\n\nPara restablecer tu contraseña, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", - "email-sent": "Correo enviado", - "email-verifyEmail-subject": "Verifica tu dirección de correo en __siteName__", - "email-verifyEmail-text": "Hola __user__,\n\nPara verificar tu cuenta de correo electrónico, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", - "enable-wip-limit": "Habilitar el límite del trabajo en proceso", - "error-board-doesNotExist": "El tablero no existe", - "error-board-notAdmin": "Es necesario ser administrador de este tablero para hacer eso", - "error-board-notAMember": "Es necesario ser miembro de este tablero para hacer eso", - "error-json-malformed": "El texto no es un JSON válido", - "error-json-schema": "Sus datos JSON no incluyen la información apropiada en el formato correcto", - "error-list-doesNotExist": "La lista no existe", - "error-user-doesNotExist": "El usuario no existe", - "error-user-notAllowSelf": "No puedes invitarte a ti mismo", - "error-user-notCreated": "El usuario no ha sido creado", - "error-username-taken": "Este nombre de usuario ya está en uso", - "error-email-taken": "Esta dirección de correo ya está en uso", - "export-board": "Exportar el tablero", - "sort": "Ordenar", - "sort-desc": "Click para ordenar la lista", - "list-sort-by": "Ordenar la lista por:", - "list-label-modifiedAt": "Hora de último acceso", - "list-label-title": "Nombre de la lista", - "list-label-sort": "Tu orden manual", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filtrar", - "filter-cards": "Filtrar tarjetas o listas", - "list-filter-label": "Filtrar listas por título", - "filter-clear": "Limpiar el filtro", - "filter-no-label": "Sin etiqueta", - "filter-no-member": "Sin miembro", - "filter-no-custom-fields": "Sin campos personalizados", - "filter-show-archive": "Mostrar las listas archivadas", - "filter-hide-empty": "Ocultar las listas vacías", - "filter-on": "Filtrado activado", - "filter-on-desc": "Estás filtrando tarjetas en este tablero. Haz clic aquí para editar el filtro.", - "filter-to-selection": "Filtrar la selección", - "advanced-filter-label": "Filtrado avanzado", - "advanced-filter-description": "El filtrado avanzado permite escribir una cadena que contiene los siguientes operadores: == != <= >= && || ( ) Se utiliza un espacio como separador entre los operadores. Se pueden filtrar todos los campos personalizados escribiendo sus nombres y valores. Por ejemplo: Campo1 == Valor1. Nota: Si los campos o valores contienen espacios, deben encapsularse entre comillas simples. Por ejemplo: 'Campo 1' == 'Valor 1'. Para omitir los caracteres de control único (' \\/), se usa \\. Por ejemplo: Campo1 = I\\'m. También se pueden combinar múltiples condiciones. Por ejemplo: C1 == V1 || C1 == V2. Normalmente todos los operadores se interpretan de izquierda a derecha. Se puede cambiar el orden colocando paréntesis. Por ejemplo: C1 == V1 && ( C2 == V2 || C2 == V3 ). También se puede buscar en campos de texto usando expresiones regulares: C1 == /Tes.*/i", - "fullname": "Nombre completo", - "header-logo-title": "Volver a tu página de tableros", - "hide-system-messages": "Ocultar las notificaciones de actividad", - "headerBarCreateBoardPopup-title": "Crear tablero", - "home": "Inicio", - "import": "Importar", - "link": "Enlace", - "import-board": "importar un tablero", - "import-board-c": "Importar un tablero", - "import-board-title-trello": "Importar un tablero desde Trello", - "import-board-title-wekan": "Importar tablero desde una exportación previa", - "import-sandstorm-backup-warning": "No elimine los datos que está importando del tablero o Trello original antes de verificar que la semilla pueda cerrarse y abrirse nuevamente, o que ocurra un error de \"Tablero no encontrado\", de lo contrario perderá sus datos.", - "import-sandstorm-warning": "El tablero importado eliminará todos los datos existentes en este tablero y los reemplazará con los datos del tablero importado.", - "from-trello": "Desde Trello", - "from-wekan": "Desde exportación previa", - "import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego 'Más' > 'Imprimir y exportar' > 'Exportar JSON', y copia el texto resultante.", - "import-board-instruction-wekan": "En tu tablero, vete a 'Menú', luego 'Exportar tablero', y copia el texto en el archivo descargado.", - "import-board-instruction-about-errors": "Aunque obtengas errores cuando importes el tablero, a veces la importación funciona igualmente, y el tablero se encontrará en la página de tableros.", - "import-json-placeholder": "Pega tus datos JSON válidos aquí", - "import-map-members": "Mapa de miembros", - "import-members-map": "Tu tablero importado tiene algunos miembros. Por favor, mapea los miembros que quieres importar con tus usuarios.", - "import-show-user-mapping": "Revisión de la asignación de miembros", - "import-user-select": "Selecciona el miembro existe que quieres usar como este miembro.", - "importMapMembersAddPopup-title": "Seleccionar miembro", - "info": "Versión", - "initials": "Iniciales", - "invalid-date": "Fecha no válida", - "invalid-time": "Tiempo no válido", - "invalid-user": "Usuario no válido", - "joined": "se ha unido", - "just-invited": "Has sido invitado a este tablero", - "keyboard-shortcuts": "Atajos de teclado", - "label-create": "Crear una etiqueta", - "label-default": "etiqueta %s (por defecto)", - "label-delete-pop": "Se eliminará esta etiqueta de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", - "labels": "Etiquetas", - "language": "Cambiar el idioma", - "last-admin-desc": "No puedes cambiar roles porque debe haber al menos un administrador.", - "leave-board": "Abandonar el tablero", - "leave-board-pop": "¿Seguro que quieres abandonar __boardTitle__? Serás desvinculado de todas las tarjetas en este tablero.", - "leaveBoardPopup-title": "¿Abandonar el tablero?", - "link-card": "Enlazar a esta tarjeta", - "list-archive-cards": "Archivar todas las tarjetas de esta lista", - "list-archive-cards-pop": "Esto eliminará del tablero todas las tarjetas en esta lista. Para ver las tarjetas en el Archivo y recuperarlas al tablero haga click en \"Menu\" > \"Archivo\"", - "list-move-cards": "Mover todas las tarjetas de esta lista", - "list-select-cards": "Seleccionar todas las tarjetas de esta lista", - "set-color-list": "Cambiar el color", - "listActionPopup-title": "Acciones de la lista", - "swimlaneActionPopup-title": "Acciones del carril de flujo", - "swimlaneAddPopup-title": "Añadir un carril de flujo debajo", - "listImportCardPopup-title": "Importar una tarjeta de Trello", - "listMorePopup-title": "Más", - "link-list": "Enlazar a esta lista", - "list-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar la lista. Esta acción no puede deshacerse.", - "list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.", - "lists": "Listas", - "swimlanes": "Carriles", - "log-out": "Finalizar la sesión", - "log-in": "Iniciar sesión", - "loginPopup-title": "Iniciar sesión", - "memberMenuPopup-title": "Preferencias de miembro", - "members": "Miembros", - "menu": "Menú", - "move-selection": "Mover la selección", - "moveCardPopup-title": "Mover la tarjeta", - "moveCardToBottom-title": "Mover al final", - "moveCardToTop-title": "Mover al principio", - "moveSelectionPopup-title": "Mover la selección", - "multi-selection": "Selección múltiple", - "multi-selection-on": "Selección múltiple activada", - "muted": "Silenciado", - "muted-info": "No serás notificado de ningún cambio en este tablero", - "my-boards": "Mis tableros", - "name": "Nombre", - "no-archived-cards": "No hay tarjetas archivadas.", - "no-archived-lists": "No hay listas archivadas.", - "no-archived-swimlanes": "No hay carriles archivados.", - "no-results": "Sin resultados", - "normal": "Normal", - "normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.", - "not-accepted-yet": "La invitación no ha sido aceptada aún", - "notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participas como creador o miembro", - "notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando", - "optional": "opcional", - "or": "o", - "page-maybe-private": "Esta página puede ser privada. Es posible que puedas verla al <a href='%s'>iniciar sesión</a>.", - "page-not-found": "Página no encontrada.", - "password": "Contraseña", - "paste-or-dragdrop": "pegar o arrastrar y soltar un fichero de imagen (sólo imagen)", - "participating": "Participando", - "preview": "Previsualizar", - "previewAttachedImagePopup-title": "Previsualizar", - "previewClipboardImagePopup-title": "Previsualizar", - "private": "Privado", - "private-desc": "Este tablero es privado. Sólo las personas añadidas al tablero pueden verlo y editarlo.", - "profile": "Perfil", - "public": "Público", - "public-desc": "Este tablero es público. Es visible para cualquiera a través del enlace, y se mostrará en los buscadores como Google. Sólo las personas añadidas al tablero pueden editarlo.", - "quick-access-description": "Destaca un tablero para añadir un acceso directo en esta barra.", - "remove-cover": "Eliminar portada", - "remove-from-board": "Desvincular del tablero", - "remove-label": "Eliminar la etiqueta", - "listDeletePopup-title": "¿Eliminar la lista?", - "remove-member": "Eliminar miembro", - "remove-member-from-card": "Eliminar de la tarjeta", - "remove-member-pop": "¿Eliminar __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero. En ellas se mostrará una notificación.", - "removeMemberPopup-title": "¿Eliminar miembro?", - "rename": "Renombrar", - "rename-board": "Renombrar el tablero", - "restore": "Restaurar", - "save": "Añadir", - "search": "Buscar", - "rules": "Reglas", - "search-cards": "Buscar entre los títulos, las descripciones de las tarjetas/listas y los campos personalizados en este tablero. ", - "search-example": "¿Texto a buscar?", - "select-color": "Seleccionar el color", - "set-wip-limit-value": "Cambiar el límite para el número máximo de tareas en esta lista.", - "setWipLimitPopup-title": "Cambiar el límite del trabajo en proceso", - "shortcut-assign-self": "Asignarte a ti mismo a la tarjeta actual", - "shortcut-autocomplete-emoji": "Autocompletar emoji", - "shortcut-autocomplete-members": "Autocompletar miembros", - "shortcut-clear-filters": "Limpiar todos los filtros", - "shortcut-close-dialog": "Cerrar el cuadro de diálogo", - "shortcut-filter-my-cards": "Filtrar mis tarjetas", - "shortcut-show-shortcuts": "Mostrar esta lista de atajos", - "shortcut-toggle-filterbar": "Conmutar la barra lateral del filtro", - "shortcut-toggle-sidebar": "Conmutar la barra lateral del tablero", - "show-cards-minimum-count": "Mostrar recuento de tarjetas si la lista contiene más de", - "sidebar-open": "Abrir la barra lateral", - "sidebar-close": "Cerrar la barra lateral", - "signupPopup-title": "Crear una cuenta", - "star-board-title": "Haz clic para destacar este tablero. Se mostrará en la parte superior de tu lista de tableros.", - "starred-boards": "Tableros destacados", - "starred-boards-description": "Los tableros destacados se mostrarán en la parte superior de tu lista de tableros.", - "subscribe": "Suscribirse", - "team": "Equipo", - "this-board": "este tablero", - "this-card": "esta tarjeta", - "spent-time-hours": "Tiempo consumido (horas)", - "overtime-hours": "Tiempo excesivo (horas)", - "overtime": "Tiempo excesivo", - "has-overtime-cards": "Hay tarjetas con el tiempo excedido", - "has-spenttime-cards": "Se ha excedido el tiempo de las tarjetas", - "time": "Hora", - "title": "Título", - "tracking": "Siguiendo", - "tracking-info": "Serás notificado de cualquier cambio en las tarjetas en las que participas como creador o miembro.", - "type": "Tipo", - "unassign-member": "Desvincular al miembro", - "unsaved-description": "Tienes una descripción por añadir.", - "unwatch": "Dejar de vigilar", - "upload": "Cargar", - "upload-avatar": "Cargar un avatar", - "uploaded-avatar": "Avatar cargado", - "username": "Nombre de usuario", - "view-it": "Verla", - "warn-list-archived": "advertencia: esta tarjeta está en una lista en el Archivo", - "watch": "Vigilar", - "watching": "Vigilando", - "watching-info": "Serás notificado de cualquier cambio en este tablero", - "welcome-board": "Tablero de bienvenida", - "welcome-swimlane": "Hito 1", - "welcome-list1": "Básicos", - "welcome-list2": "Avanzados", - "card-templates-swimlane": "Plantilla de tarjeta", - "list-templates-swimlane": "Listar plantillas", - "board-templates-swimlane": "Plantilla de tablero", - "what-to-do": "¿Qué quieres hacer?", - "wipLimitErrorPopup-title": "El límite del trabajo en proceso no es válido.", - "wipLimitErrorPopup-dialog-pt1": "El número de tareas en esta lista es mayor que el límite del trabajo en proceso que has definido.", - "wipLimitErrorPopup-dialog-pt2": "Por favor, mueve algunas tareas fuera de esta lista, o fija un límite del trabajo en proceso más alto.", - "admin-panel": "Panel del administrador", - "settings": "Preferencias", - "people": "Personas", - "registration": "Registro", - "disable-self-registration": "Deshabilitar autoregistro", - "invite": "Invitar", - "invite-people": "Invitar a personas", - "to-boards": "A el(los) tablero(s)", - "email-addresses": "Direcciones de correo electrónico", - "smtp-host-description": "Dirección del servidor SMTP para gestionar tus correos", - "smtp-port-description": "Puerto usado por el servidor SMTP para mandar correos", - "smtp-tls-description": "Habilitar el soporte TLS para el servidor SMTP", - "smtp-host": "Servidor SMTP", - "smtp-port": "Puerto SMTP", - "smtp-username": "Nombre de usuario", - "smtp-password": "Contraseña", - "smtp-tls": "Soporte TLS", - "send-from": "Desde", - "send-smtp-test": "Enviarte un correo de prueba a ti mismo", - "invitation-code": "Código de Invitación", - "email-invite-register-subject": "__inviter__ te ha enviado una invitación", - "email-invite-register-text": "Querido __user__,\n__inviter__ le invita al tablero kanban para colaborar.\n\nPor favor, siga el siguiente enlace:\n__url__\n\nY tu código de invitación es: __icode__\n\nGracias.", - "email-smtp-test-subject": "Prueba de email SMTP", - "email-smtp-test-text": "El correo se ha enviado correctamente", - "error-invitation-code-not-exist": "El código de invitación no existe", - "error-notAuthorized": "No estás autorizado a ver esta página.", - "webhook-title": "Nombre del Webhook", - "webhook-token": "Token (opcional para la autenticación)", - "outgoing-webhooks": "Webhooks salientes", - "bidirectional-webhooks": "Webhooks de doble sentido", - "outgoingWebhooksPopup-title": "Webhooks salientes", - "boardCardTitlePopup-title": "Filtro de títulos de tarjeta", - "disable-webhook": "Deshabilitar este Webhook", - "global-webhook": "Webhooks globales", - "new-outgoing-webhook": "Nuevo webhook saliente", - "no-name": "(Desconocido)", - "Node_version": "Versión de Node", - "Meteor_version": "Versión de Meteor", - "MongoDB_version": "Versión de MongoDB", - "MongoDB_storage_engine": "Motor de almacenamiento de MongoDB", - "MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado", - "OS_Arch": "Arquitectura del sistema", - "OS_Cpus": "Número de CPUs del sistema", - "OS_Freemem": "Memoria libre del sistema", - "OS_Loadavg": "Carga media del sistema", - "OS_Platform": "Plataforma del sistema", - "OS_Release": "Publicación del sistema", - "OS_Totalmem": "Memoria total del sistema", - "OS_Type": "Tipo de sistema", - "OS_Uptime": "Tiempo activo del sistema", - "days": "días", - "hours": "horas", - "minutes": "minutos", - "seconds": "segundos", - "show-field-on-card": "Mostrar este campo en la tarjeta", - "automatically-field-on-card": "Crear campos automáticamente para todas las tarjetas.", - "showLabel-field-on-card": "Mostrar etiquetas de campos en la minitarjeta.", - "yes": "Sí", - "no": "No", - "accounts": "Cuentas", - "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", - "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", - "createdAt": "Fecha de alta", - "verified": "Verificado", - "active": "Activo", - "card-received": "Recibido", - "card-received-on": "Recibido el", - "card-end": "Finalizado", - "card-end-on": "Finalizado el", - "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", - "editCardEndDatePopup-title": "Cambiar la fecha de finalización", - "setCardColorPopup-title": "Cambiar el color", - "setCardActionsColorPopup-title": "Elegir un color", - "setSwimlaneColorPopup-title": "Elegir un color", - "setListColorPopup-title": "Elegir un color", - "assigned-by": "Asignado por", - "requested-by": "Solicitado por", - "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", - "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", - "boardDeletePopup-title": "¿Eliminar el tablero?", - "delete-board": "Eliminar el tablero", - "default-subtasks-board": "Subtareas para el tablero __board__", - "default": "Por defecto", - "queue": "Cola", - "subtask-settings": "Preferencias de las subtareas", - "card-settings": "Preferencias de la tarjeta", - "boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero", - "boardCardSettingsPopup-title": "Preferencias de la tarjeta", - "deposit-subtasks-board": "Depositar subtareas en este tablero:", - "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:", - "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:", - "prefix-with-full-path": "Prefijo con ruta completa", - "prefix-with-parent": "Prefijo con el padre", - "subtext-with-full-path": "Subtexto con ruta completa", - "subtext-with-parent": "Subtexto con el padre", - "change-card-parent": "Cambiar la tarjeta padre", - "parent-card": "Tarjeta padre", - "source-board": "Tablero de origen", - "no-parent": "No mostrar la tarjeta padre", - "activity-added-label": "añadida etiqueta %s a %s", - "activity-removed-label": "eliminada etiqueta '%s' desde %s", - "activity-delete-attach": "eliminado un adjunto desde %s", - "activity-added-label-card": "añadida etiqueta '%s'", - "activity-removed-label-card": "eliminada etiqueta '%s'", - "activity-delete-attach-card": "eliminado un adjunto", - "activity-set-customfield": "Cambiar el campo personalizado '%s' a '%s' en %s", - "activity-unset-customfield": "Desmarcar el campo personalizado '%s' en %s", - "r-rule": "Regla", - "r-add-trigger": "Añadir disparador", - "r-add-action": "Añadir acción", - "r-board-rules": "Reglas del tablero", - "r-add-rule": "Añadir regla", - "r-view-rule": "Ver regla", - "r-delete-rule": "Eliminar regla", - "r-new-rule-name": "Nueva título de regla", - "r-no-rules": "No hay reglas", - "r-when-a-card": "Cuando una tarjeta", - "r-is": "es", - "r-is-moved": "es movida", - "r-added-to": "agregada a", - "r-removed-from": "eliminado de", - "r-the-board": "el tablero", - "r-list": "la lista", - "set-filter": "Filtrar", - "r-moved-to": "Movido a", - "r-moved-from": "Movido desde", - "r-archived": "Se archivó", - "r-unarchived": "Restaurado del archivo", - "r-a-card": "una tarjeta", - "r-when-a-label-is": "Cuando una etiqueta es", - "r-when-the-label": "Cuando la etiqueta es", - "r-list-name": "Nombre de lista", - "r-when-a-member": "Cuando un miembro es", - "r-when-the-member": "Cuando el miembro", - "r-name": "nombre", - "r-when-a-attach": "Cuando un adjunto", - "r-when-a-checklist": "Cuando una lista de verificación es", - "r-when-the-checklist": "Cuando la lista de verificación", - "r-completed": "Completada", - "r-made-incomplete": "Hecha incompleta", - "r-when-a-item": "Cuando un elemento de la lista de verificación es", - "r-when-the-item": "Cuando el elemento de la lista de verificación es", - "r-checked": "Marcado", - "r-unchecked": "Desmarcado", - "r-move-card-to": "Mover la tarjeta", - "r-top-of": "Arriba de", - "r-bottom-of": "Abajo de", - "r-its-list": "su lista", - "r-archive": "Archivar", - "r-unarchive": "Restaurar del Archivo", - "r-card": "la tarjeta", - "r-add": "Añadir", - "r-remove": "Eliminar", - "r-label": "etiqueta", - "r-member": "miembro", - "r-remove-all": "Eliminar todos los miembros de la tarjeta", - "r-set-color": "Cambiar el color a", - "r-checklist": "lista de verificación", - "r-check-all": "Marcar todo", - "r-uncheck-all": "Desmarcar todo", - "r-items-check": "elementos de la lista de verificación", - "r-check": "Marcar", - "r-uncheck": "Desmarcar", - "r-item": "elemento", - "r-of-checklist": "de la lista de verificación", - "r-send-email": "Enviar un email", - "r-to": "a", - "r-subject": "asunto", - "r-rule-details": "Detalle de la regla", - "r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista", - "r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista", - "r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista", - "r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista", - "r-d-send-email": "Enviar email", - "r-d-send-email-to": "a", - "r-d-send-email-subject": "asunto", - "r-d-send-email-message": "mensaje", - "r-d-archive": "Archivar la tarjeta", - "r-d-unarchive": "Restaurar tarjeta del Archivo", - "r-d-add-label": "Añadir etiqueta", - "r-d-remove-label": "Eliminar etiqueta", - "r-create-card": "Crear una nueva tarjeta", - "r-in-list": "en la lista", - "r-in-swimlane": "en el carril", - "r-d-add-member": "Añadir miembro", - "r-d-remove-member": "Eliminar miembro", - "r-d-remove-all-member": "Eliminar todos los miembros", - "r-d-check-all": "Marcar todos los elementos de una lista", - "r-d-uncheck-all": "Desmarcar todos los elementos de una lista", - "r-d-check-one": "Marcar elemento", - "r-d-uncheck-one": "Desmarcar elemento", - "r-d-check-of-list": "de la lista de verificación", - "r-d-add-checklist": "Añadir una lista de verificación", - "r-d-remove-checklist": "Eliminar lista de verificación", - "r-by": "por", - "r-add-checklist": "Añadir una lista de verificación", - "r-with-items": "con items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Agregar el carril", - "r-swimlane-name": "nombre del carril", - "r-board-note": "Nota: deje un campo vacío para que coincida con todos los valores posibles", - "r-checklist-note": "Nota: los ítems de la lista tienen que escribirse como valores separados por coma.", - "r-when-a-card-is-moved": "Cuando una tarjeta es movida a otra lista", - "r-set": "Cambiar", - "r-update": "Actualizar", - "r-datefield": "campo de fecha", - "r-df-start-at": "comienza", - "r-df-due-at": "vencimiento", - "r-df-end-at": "finalizado", - "r-df-received-at": "recibido", - "r-to-current-datetime": "a la fecha/hora actual", - "r-remove-value-from": "Eliminar el valor de", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Método de autenticación", - "authentication-type": "Tipo de autenticación", - "custom-product-name": "Nombre de producto personalizado", - "layout": "Diseño", - "hide-logo": "Ocultar el logo", - "add-custom-html-after-body-start": "Añade HTML personalizado después de <body>", - "add-custom-html-before-body-end": "Añade HTML personalizado después de </body>", - "error-undefined": "Algo no está bien", - "error-ldap-login": "Ocurrió un error al intentar acceder", - "display-authentication-method": "Mostrar el método de autenticación", - "default-authentication-method": "Método de autenticación por defecto", - "duplicate-board": "Duplicar tablero", - "people-number": "El número de personas es:", - "swimlaneDeletePopup-title": "¿Eliminar el carril de flujo?", - "swimlane-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar el carril de flujo. Esta acción no puede deshacerse.", - "restore-all": "Restaurar todas", - "delete-all": "Borrar todas", - "loading": "Cargando. Por favor, espere.", - "previous_as": "el último tiempo fue", - "act-a-dueAt": "cambiada la hora de vencimiento a \nCuándo: __timeValue__\nDónde: __card__\n el vencimiento anterior fue __timeOldValue__", - "act-a-endAt": "cambiada la hora de finalización a __timeValue__ Fecha anterior: (__timeOldValue__)", - "act-a-startAt": "cambiada la hora de comienzo a __timeValue__ Fecha anterior: (__timeOldValue__)", - "act-a-receivedAt": "cambiada la fecha de recepción a __timeValue__ Fecha anterior: (__timeOldValue__)", - "a-dueAt": "cambiada la hora de vencimiento a", - "a-endAt": "cambiada la hora de finalización a", - "a-startAt": "cambiada la hora de comienzo a", - "a-receivedAt": "cambiada la hora de recepción a", - "almostdue": "está próxima la hora de vencimiento actual %s", - "pastdue": "se sobrepasó la hora de vencimiento actual%s", - "duenow": "la hora de vencimiento actual %s es hoy", - "act-newDue": "__list__/__card__ tiene una 1ra notificación de vencimiento [__board__]", - "act-withDue": "__list__/__card__ notificaciones de vencimiento [__board__]", - "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", - "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", - "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", - "act-atUserComment": "Se te mencionó en [__board__] __list__/__card__", - "delete-user-confirm-popup": "¿Seguro que quieres eliminar esta cuenta? Esta acción no puede deshacerse.", - "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta", - "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta", - "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio", - "assignee": "Asignado", - "cardAssigneesPopup-title": "Asignado", - "addmore-detail": "Añadir una descripción detallada", - "show-on-card": "Mostrar en la tarjeta", - "new": "Nuevo", - "editUserPopup-title": "Editar el usuario", - "newUserPopup-title": "Nuevo usuario", - "notifications": "Notificaciones", - "view-all": "Ver todo", - "filter-by-unread": "Filtrar por no leído", - "mark-all-as-read": "Marcar todo como leido", - "allow-rename": "Permitir renombrar", - "allowRenamePopup-title": "Permitir renombrar" -} + "accept": "Aceptar", + "act-activity-notify": "Notificación de actividad", + "act-addAttachment": "añadido el adjunto __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteAttachment": "eliminado el adjunto __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addSubtask": "añadida la subtarea __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addedLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removedLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklist": "añadida la lista de verificación __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addChecklistItem": "añadido el elemento __checklistItem__ a la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklist": "eliminada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeChecklistItem": "eliminado el elemento __checklistItem__ de la lista de verificación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-checkedItem": "marcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncheckedItem": "desmarcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-completeChecklist": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-uncompleteChecklist": "no completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createBoard": "creó el tablero __board__", + "act-createSwimlane": "creó el carril de flujo __swimlane__ en el tablero __board__", + "act-createCard": "creada la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createCustomField": " creado el campo personalizado __customField__ en el tablero __board__", + "act-deleteCustomField": "eliminado el campo personalizado __customField__ del tablero __board__", + "act-setCustomField": "editado el campo personalizado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-createList": "añadida la lista __list__ al tablero __board__", + "act-addBoardMember": "añadido el mimbro __member__ al tablero __board__", + "act-archivedBoard": "El tablero __board__ se ha archivado", + "act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado", + "act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado", + "act-importBoard": "importado el tablero __board__", + "act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__", + "act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__", + "act-joinMember": "añadido el miembro __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-moveCard": "movida la tarjeta __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__", + "act-moveCardToOtherBoard": "movida la tarjeta __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-removeBoardMember": "eliminado el miembro __member__ del tablero __board__", + "act-restoredCard": "restaurada la tarjeta __card__ a la lista __list__ del carril __swimlane__ del tablero __board__", + "act-unjoinMember": "eliminado el miembro __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Acciones", + "activities": "Actividades", + "activity": "Actividad", + "activity-added": "ha añadido %s a %s", + "activity-archived": "%s se ha archivado", + "activity-attached": "ha adjuntado %s a %s", + "activity-created": "ha creado %s", + "activity-customfield-created": "creó el campo personalizado %s", + "activity-excluded": "ha excluido %s de %s", + "activity-imported": "ha importado %s a %s desde %s", + "activity-imported-board": "ha importado %s desde %s", + "activity-joined": "se ha unido a %s", + "activity-moved": "ha movido %s de %s a %s", + "activity-on": "en %s", + "activity-removed": "ha eliminado %s de %s", + "activity-sent": "ha enviado %s a %s", + "activity-unjoined": "se ha desvinculado de %s", + "activity-subtask-added": "ha añadido la subtarea a %s", + "activity-checked-item": "marcado %s en la lista de verificación %s de %s", + "activity-unchecked-item": "desmarcado %s en lista %s de %s", + "activity-checklist-added": "ha añadido una lista de verificación a %s", + "activity-checklist-removed": "eliminada una lista de verificación desde %s", + "activity-checklist-completed": "lista de verificación completada %s de %s", + "activity-checklist-uncompleted": "no completado la lista %s de %s", + "activity-checklist-item-added": "ha añadido el elemento de la lista de verificación a '%s' en %s", + "activity-checklist-item-removed": "eliminado un elemento de la lista de verificación desde '%s' en %s", + "add": "Añadir", + "activity-checked-item-card": "marcado %s en la lista de verificación %s", + "activity-unchecked-item-card": "desmarcado %s en la lista de verificación %s", + "activity-checklist-completed-card": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__", + "activity-checklist-uncompleted-card": "no completó la lista de verificación %s", + "activity-editComment": "comentario editado", + "activity-deleteComment": "comentario eliminado", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Añadir adjunto", + "add-board": "Añadir tablero", + "add-template": "Añadir plantilla", + "add-card": "Añadir una tarjeta", + "add-card-to-top-of-list": "Subir la tarjeta al principio de la lista", + "add-card-to-bottom-of-list": "Bajar la tarjeta al final de la lista", + "add-swimlane": "Añadir un carril de flujo", + "add-subtask": "Añadir subtarea", + "add-checklist": "Añadir una lista de verificación", + "add-checklist-item": "Añadir un elemento a la lista de verificación", + "add-cover": "Añadir portada", + "add-label": "Añadir una etiqueta", + "add-list": "Añadir una lista", + "add-members": "Añadir miembros", + "added": "Añadida el", + "addMemberPopup-title": "Miembros", + "admin": "Administrador", + "admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero", + "admin-announcement": "Aviso", + "admin-announcement-active": "Activar el aviso para todo el sistema", + "admin-announcement-title": "Aviso del administrador", + "all-boards": "Tableros", + "and-n-other-card": "y __count__ tarjeta más", + "and-n-other-card_plural": "y otras __count__ tarjetas", + "apply": "Aplicar", + "app-is-offline": "Cargando, espera por favor. Refrescar esta página causará pérdida de datos. Si la carga no funciona, por favor comprueba que el servidor no se ha parado.", + "archive": "Archivar", + "archive-all": "Archivar todo", + "archive-board": "Archivar este tablero", + "archive-card": "Archivar esta tarjeta", + "archive-list": "Archivar esta lista", + "archive-swimlane": "Archivar este carril", + "archive-selection": "Archivar esta selección", + "archiveBoardPopup-title": "¿Archivar este tablero?", + "archived-items": "Archivo", + "archived-boards": "Tableros en el Archivo", + "restore-board": "Restaurar el tablero", + "no-archived-boards": "No hay Tableros en el Archivo", + "archives": "Archivo", + "template": "Plantilla", + "templates": "Plantillas", + "template-container": "Plantilla de contenedor", + "add-template-container": "añadir plantilla de contenedor", + "assign-member": "Asignar miembros", + "attached": "adjuntado", + "attachment": "Adjunto", + "attachment-delete-pop": "La eliminación de un fichero adjunto es permanente. Esta acción no puede deshacerse.", + "attachmentDeletePopup-title": "¿Eliminar el adjunto?", + "attachments": "Adjuntos", + "auto-watch": "Suscribirse automáticamente a los tableros cuando son creados", + "avatar-too-big": "El avatar es demasiado grande (520KB máx.)", + "back": "Atrás", + "board-change-color": "Cambiar el color", + "board-nb-stars": "%s destacados", + "board-not-found": "Tablero no encontrado", + "board-private-info": "Este tablero será <strong>privado</strong>.", + "board-public-info": "Este tablero será <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Mueve y posiciona para reordenar los iconos de los tableros. Click en el icono del tablero para abrirlo", + "boardChangeColorPopup-title": "Cambiar el fondo del tablero", + "boardChangeTitlePopup-title": "Renombrar el tablero", + "boardChangeVisibilityPopup-title": "Cambiar visibilidad", + "boardChangeWatchPopup-title": "Cambiar vigilancia", + "boardMenuPopup-title": "Preferencias del tablero", + "boardChangeViewPopup-title": "Vista del tablero", + "boards": "Tableros", + "board-view": "Vista del tablero", + "board-view-cal": "Calendario", + "board-view-swimlanes": "Carriles", + "board-view-collapse": "Contraer", + "board-view-gantt": "Gantt", + "board-view-lists": "Listas", + "bucket-example": "Como “Cosas por hacer” por ejemplo", + "cancel": "Cancelar", + "card-archived": "Se archivó esta tarjeta", + "board-archived": "Se archivó este tablero", + "card-comments-title": "Esta tarjeta tiene %s comentarios.", + "card-delete-notice": "la eliminación es permanente. Perderás todas las acciones asociadas a esta tarjeta.", + "card-delete-pop": "Se eliminarán todas las acciones del historial de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.", + "card-delete-suggest-archive": "Puedes mover una tarjeta al Archivo para quitarla del tablero y preservar la actividad.", + "card-due": "Vence", + "card-due-on": "Vence el", + "card-spent": "Tiempo consumido", + "card-edit-attachments": "Editar los adjuntos", + "card-edit-custom-fields": "Editar los campos personalizados", + "card-edit-labels": "Editar las etiquetas", + "card-edit-members": "Editar los miembros", + "card-labels-title": "Cambia las etiquetas de la tarjeta", + "card-members-title": "Añadir o eliminar miembros del tablero desde la tarjeta.", + "card-start": "Comienza", + "card-start-on": "Comienza el", + "cardAttachmentsPopup-title": "Adjuntar desde", + "cardCustomField-datePopup-title": "Cambiar la fecha", + "cardCustomFieldsPopup-title": "Editar los campos personalizados", + "cardStartVotingPopup-title": "Comience a votar", + "positiveVoteMembersPopup-title": "Favorables", + "negativeVoteMembersPopup-title": "Contrarios", + "card-edit-voting": "Editar votación", + "editVoteEndDatePopup-title": "Cambie fecha de termino del voto", + "allowNonBoardMembers": "Permitir todos los usuarios autentificados", + "vote-question": "Pregunta de votación", + "vote-public": "Mostrar quien voto que", + "vote-for-it": "por esto", + "vote-against": "contrarios", + "deleteVotePopup-title": "¿Borrar voto?", + "vote-delete-pop": "El Borrado es permanente. Perderá todas las acciones asociadas con este voto.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "¿Eliminar la tarjeta?", + "cardDetailsActionsPopup-title": "Acciones de la tarjeta", + "cardLabelsPopup-title": "Etiquetas", + "cardMembersPopup-title": "Miembros", + "cardMorePopup-title": "Más", + "cardTemplatePopup-title": "Crear plantilla", + "cards": "Tarjetas", + "cards-count": "Tarjetas", + "cards-count-one": "Tarjeta", + "casSignIn": "Iniciar sesión con CAS", + "cardType-card": "Tarjeta", + "cardType-linkedCard": "Tarjeta enlazada", + "cardType-linkedBoard": "Tablero enlazado", + "change": "Cambiar", + "change-avatar": "Cambiar el avatar", + "change-password": "Cambiar la contraseña", + "change-permissions": "Cambiar los permisos", + "change-settings": "Cambiar las preferencias", + "changeAvatarPopup-title": "Cambiar el avatar", + "changeLanguagePopup-title": "Cambiar el idioma", + "changePasswordPopup-title": "Cambiar la contraseña", + "changePermissionsPopup-title": "Cambiar los permisos", + "changeSettingsPopup-title": "Cambiar las preferencias", + "subtasks": "Subtareas", + "checklists": "Lista de verificación", + "click-to-star": "Haz clic para destacar este tablero.", + "click-to-unstar": "Haz clic para dejar de destacar este tablero.", + "clipboard": "el portapapeles o con arrastrar y soltar", + "close": "Cerrar", + "close-board": "Cerrar el tablero", + "close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.", + "close-card": "Close Card", + "color-black": "negra", + "color-blue": "azul", + "color-crimson": "carmesí", + "color-darkgreen": "verde oscuro", + "color-gold": "oro", + "color-gray": "gris", + "color-green": "verde", + "color-indigo": "añil", + "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "rosa claro", + "color-navy": "azul marino", + "color-orange": "naranja", + "color-paleturquoise": "turquesa", + "color-peachpuff": "melocotón", + "color-pink": "rosa", + "color-plum": "púrpura", + "color-purple": "violeta", + "color-red": "roja", + "color-saddlebrown": "marrón", + "color-silver": "plata", + "color-sky": "celeste", + "color-slateblue": "azul", + "color-white": "blanco", + "color-yellow": "amarilla", + "unset-color": "Desmarcar", + "comment": "Comentar", + "comment-placeholder": "Escribir comentario", + "comment-only": "Sólo comentarios", + "comment-only-desc": "Solo puedes comentar en las tarjetas.", + "no-comments": "No hay comentarios", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", + "worker": "Trabajador", + "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.", + "computer": "el ordenador", + "confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?", + "confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?", + "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", + "linkCardPopup-title": "Enlazar tarjeta", + "searchElementPopup-title": "Buscar", + "copyCardPopup-title": "Copiar la tarjeta", + "copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de verificación en varias tarjetas", + "copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", + "create": "Crear", + "createBoardPopup-title": "Crear tablero", + "chooseBoardSourcePopup-title": "Importar un tablero", + "createLabelPopup-title": "Crear etiqueta", + "createCustomField": "Crear un campo", + "createCustomFieldPopup-title": "Crear un campo", + "current": "actual", + "custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "custom-field-checkbox": "Casilla de verificación", + "custom-field-currency": "Moneda", + "custom-field-currency-option": "Código de moneda", + "custom-field-date": "Fecha", + "custom-field-dropdown": "Lista desplegable", + "custom-field-dropdown-none": "(nada)", + "custom-field-dropdown-options": "Opciones de la lista", + "custom-field-dropdown-options-placeholder": "Pulsa Intro para añadir más opciones", + "custom-field-dropdown-unknown": "(desconocido)", + "custom-field-number": "Número", + "custom-field-text": "Texto", + "custom-fields": "Campos personalizados", + "date": "Fecha", + "decline": "Declinar", + "default-avatar": "Avatar por defecto", + "delete": "Eliminar", + "deleteCustomFieldPopup-title": "¿Eliminar el campo personalizado?", + "deleteLabelPopup-title": "¿Eliminar la etiqueta?", + "description": "Descripción", + "disambiguateMultiLabelPopup-title": "Desambiguar la acción de etiqueta", + "disambiguateMultiMemberPopup-title": "Desambiguar la acción de miembro", + "discard": "Descartarla", + "done": "Hecho", + "download": "Descargar", + "edit": "Editar", + "edit-avatar": "Cambiar el avatar", + "edit-profile": "Editar el perfil", + "edit-wip-limit": "Cambiar el límite del trabajo en proceso", + "soft-wip-limit": "Límite del trabajo en proceso flexible", + "editCardStartDatePopup-title": "Cambiar la fecha de comienzo", + "editCardDueDatePopup-title": "Cambiar la fecha de vencimiento", + "editCustomFieldPopup-title": "Editar el campo", + "editCardSpentTimePopup-title": "Cambiar el tiempo consumido", + "editLabelPopup-title": "Cambiar la etiqueta", + "editNotificationPopup-title": "Editar las notificaciones", + "editProfilePopup-title": "Editar el perfil", + "email": "Correo electrónico", + "email-enrollAccount-subject": "Cuenta creada en __siteName__", + "email-enrollAccount-text": "Hola __user__,\n\nPara empezar a utilizar el servicio, simplemente haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-fail": "Error al enviar el correo", + "email-fail-text": "Error al intentar enviar el correo", + "email-invalid": "Correo no válido", + "email-invite": "Invitar vía correo electrónico", + "email-invite-subject": "__inviter__ ha enviado una invitación", + "email-invite-text": "Estimado __user__,\n\n__inviter__ te invita a unirte al tablero '__board__' para colaborar.\n\nPor favor, haz clic en el siguiente enlace:\n\n__url__\n\nGracias.", + "email-resetPassword-subject": "Restablecer tu contraseña en __siteName__", + "email-resetPassword-text": "Hola __user__,\n\nPara restablecer tu contraseña, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "email-sent": "Correo enviado", + "email-verifyEmail-subject": "Verifica tu dirección de correo en __siteName__", + "email-verifyEmail-text": "Hola __user__,\n\nPara verificar tu cuenta de correo electrónico, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.", + "enable-wip-limit": "Habilitar el límite del trabajo en proceso", + "error-board-doesNotExist": "El tablero no existe", + "error-board-notAdmin": "Es necesario ser administrador de este tablero para hacer eso", + "error-board-notAMember": "Es necesario ser miembro de este tablero para hacer eso", + "error-json-malformed": "El texto no es un JSON válido", + "error-json-schema": "Sus datos JSON no incluyen la información apropiada en el formato correcto", + "error-csv-schema": "Su CSV(Valores separados por coma)/TSV(Valores separados por tab) no incluyen la información apropiada en el formato correcto", + "error-list-doesNotExist": "La lista no existe", + "error-user-doesNotExist": "El usuario no existe", + "error-user-notAllowSelf": "No puedes invitarte a ti mismo", + "error-user-notCreated": "El usuario no ha sido creado", + "error-username-taken": "Este nombre de usuario ya está en uso", + "error-orgname-taken": "Este nombre de organización ya está en uso", + "error-teamname-taken": "Este nombre de equipo ya está en uso", + "error-email-taken": "Esta dirección de correo ya está en uso", + "export-board": "Exportar el tablero", + "export-board-json": "Exportar tablero a JSON", + "export-board-csv": "Exportar tablero a CSV", + "export-board-tsv": "Exportar tablero a TSV", + "export-board-excel": "Exportar tablero a Excel", + "user-can-not-export-excel": "El usuario no puede exportar Excel", + "export-board-html": "Exportar tablero a HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar el tablero", + "exportCardPopup-title": "Export card", + "sort": "Ordenar", + "sort-desc": "Click para ordenar la lista", + "list-sort-by": "Ordenar la lista por:", + "list-label-modifiedAt": "Hora de último acceso", + "list-label-title": "Nombre de la lista", + "list-label-sort": "Tu orden manual", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filtrar", + "filter-cards": "Filtrar tarjetas o listas", + "filter-dates-label": "Filtrar por fecha", + "filter-no-due-date": "Sin fecha de vencimiento", + "filter-overdue": "Atrasado", + "filter-due-today": "Vence hoy", + "filter-due-this-week": "Vence esta semana", + "filter-due-tomorrow": "Vence mañana", + "list-filter-label": "Filtrar listas por título", + "filter-clear": "Limpiar el filtro", + "filter-labels-label": "Filtrar por etiqueta", + "filter-no-label": "Sin etiqueta", + "filter-member-label": "Filtrar por miembro", + "filter-no-member": "Sin miembro", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No asignado", + "filter-custom-fields-label": "Filtrar por campos personalizados", + "filter-no-custom-fields": "Sin campos personalizados", + "filter-show-archive": "Mostrar las listas archivadas", + "filter-hide-empty": "Ocultar las listas vacías", + "filter-on": "Filtrado activado", + "filter-on-desc": "Estás filtrando tarjetas en este tablero. Haz clic aquí para editar el filtro.", + "filter-to-selection": "Filtrar la selección", + "other-filters-label": "Otros filtros", + "advanced-filter-label": "Filtrado avanzado", + "advanced-filter-description": "El filtrado avanzado permite escribir una cadena que contiene los siguientes operadores: == != <= >= && || ( ) Se utiliza un espacio como separador entre los operadores. Se pueden filtrar todos los campos personalizados escribiendo sus nombres y valores. Por ejemplo: Campo1 == Valor1. Nota: Si los campos o valores contienen espacios, deben encapsularse entre comillas simples. Por ejemplo: 'Campo 1' == 'Valor 1'. Para omitir los caracteres de control único (' \\/), se usa \\. Por ejemplo: Campo1 = I\\'m. También se pueden combinar múltiples condiciones. Por ejemplo: C1 == V1 || C1 == V2. Normalmente todos los operadores se interpretan de izquierda a derecha. Se puede cambiar el orden colocando paréntesis. Por ejemplo: C1 == V1 && ( C2 == V2 || C2 == V3 ). También se puede buscar en campos de texto usando expresiones regulares: C1 == /Tes.*/i", + "fullname": "Nombre completo", + "header-logo-title": "Volver a tu página de tableros", + "hide-system-messages": "Ocultar las notificaciones de actividad", + "headerBarCreateBoardPopup-title": "Crear tablero", + "home": "Inicio", + "import": "Importar", + "impersonate-user": "Impersonate user", + "link": "Enlace", + "import-board": "importar un tablero", + "import-board-c": "Importar un tablero", + "import-board-title-trello": "Importar un tablero desde Trello", + "import-board-title-wekan": "Importar tablero desde una exportación previa", + "import-board-title-csv": "Importar tablero desde CSV/TSV", + "from-trello": "Desde Trello", + "from-wekan": "Desde exportación previa", + "from-csv": "Desde CSV/TSV", + "import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego 'Más' > 'Imprimir y exportar' > 'Exportar JSON', y copia el texto resultante.", + "import-board-instruction-csv": "Pegue en sus Valores separados por coma(CSV)/Valores separados por tab(TSV).", + "import-board-instruction-wekan": "En tu tablero, vete a 'Menú', luego 'Exportar tablero', y copia el texto en el archivo descargado.", + "import-board-instruction-about-errors": "Aunque obtengas errores cuando importes el tablero, a veces la importación funciona igualmente, y el tablero se encontrará en la página de tableros.", + "import-json-placeholder": "Pega tus datos JSON válidos aquí", + "import-csv-placeholder": "Pega tus datos CSV/TSV válidos aquí", + "import-map-members": "Mapa de miembros", + "import-members-map": "Tu tablero importado tiene algunos miembros. Por favor, mapea los miembros que quieres importar con tus usuarios.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Revisión de la asignación de miembros", + "import-user-select": "Selecciona el miembro existe que quieres usar como este miembro.", + "importMapMembersAddPopup-title": "Seleccionar miembro", + "info": "Versión", + "initials": "Iniciales", + "invalid-date": "Fecha no válida", + "invalid-time": "Tiempo no válido", + "invalid-user": "Usuario no válido", + "joined": "se ha unido", + "just-invited": "Has sido invitado a este tablero", + "keyboard-shortcuts": "Atajos de teclado", + "label-create": "Crear una etiqueta", + "label-default": "etiqueta %s (por defecto)", + "label-delete-pop": "Se eliminará esta etiqueta de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.", + "labels": "Etiquetas", + "language": "Cambiar el idioma", + "last-admin-desc": "No puedes cambiar roles porque debe haber al menos un administrador.", + "leave-board": "Abandonar el tablero", + "leave-board-pop": "¿Seguro que quieres abandonar __boardTitle__? Serás desvinculado de todas las tarjetas en este tablero.", + "leaveBoardPopup-title": "¿Abandonar el tablero?", + "link-card": "Enlazar a esta tarjeta", + "list-archive-cards": "Archivar todas las tarjetas de esta lista", + "list-archive-cards-pop": "Esto eliminará del tablero todas las tarjetas en esta lista. Para ver las tarjetas en el Archivo y recuperarlas al tablero haga click en \"Menu\" > \"Archivo\"", + "list-move-cards": "Mover todas las tarjetas de esta lista", + "list-select-cards": "Seleccionar todas las tarjetas de esta lista", + "set-color-list": "Cambiar el color", + "listActionPopup-title": "Acciones de la lista", + "settingsUserPopup-title": "Preferencias de usuario", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Acciones del carril de flujo", + "swimlaneAddPopup-title": "Añadir un carril de flujo debajo", + "listImportCardPopup-title": "Importar una tarjeta de Trello", + "listImportCardsTsvPopup-title": "Importar CSV/TSV", + "listMorePopup-title": "Más", + "link-list": "Enlazar a esta lista", + "list-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar la lista. Esta acción no puede deshacerse.", + "list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.", + "lists": "Listas", + "swimlanes": "Carriles", + "log-out": "Finalizar la sesión", + "log-in": "Iniciar sesión", + "loginPopup-title": "Iniciar sesión", + "memberMenuPopup-title": "Preferencias de miembro", + "members": "Miembros", + "menu": "Menú", + "move-selection": "Mover la selección", + "moveCardPopup-title": "Mover la tarjeta", + "moveCardToBottom-title": "Mover al final", + "moveCardToTop-title": "Mover al principio", + "moveSelectionPopup-title": "Mover la selección", + "multi-selection": "Selección múltiple", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Selección múltiple activada", + "muted": "Silenciado", + "muted-info": "No serás notificado de ningún cambio en este tablero", + "my-boards": "Mis tableros", + "name": "Nombre", + "no-archived-cards": "No hay tarjetas archivadas.", + "no-archived-lists": "No hay listas archivadas.", + "no-archived-swimlanes": "No hay carriles archivados.", + "no-results": "Sin resultados", + "normal": "Normal", + "normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.", + "not-accepted-yet": "La invitación no ha sido aceptada aún", + "notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participas como creador o miembro", + "notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando", + "optional": "opcional", + "or": "o", + "page-maybe-private": "Esta página puede ser privada. Es posible que puedas verla al <a href='%s'>iniciar sesión</a>.", + "page-not-found": "Página no encontrada.", + "password": "Contraseña", + "paste-or-dragdrop": "pegar o arrastrar y soltar un fichero de imagen (sólo imagen)", + "participating": "Participando", + "preview": "Previsualizar", + "previewAttachedImagePopup-title": "Previsualizar", + "previewClipboardImagePopup-title": "Previsualizar", + "private": "Privado", + "private-desc": "Este tablero es privado. Sólo las personas añadidas al tablero pueden verlo y editarlo.", + "profile": "Perfil", + "public": "Público", + "public-desc": "Este tablero es público. Es visible para cualquiera a través del enlace, y se mostrará en los buscadores como Google. Sólo las personas añadidas al tablero pueden editarlo.", + "quick-access-description": "Destaca un tablero para añadir un acceso directo en esta barra.", + "remove-cover": "Eliminar portada", + "remove-from-board": "Desvincular del tablero", + "remove-label": "Eliminar la etiqueta", + "listDeletePopup-title": "¿Eliminar la lista?", + "remove-member": "Eliminar miembro", + "remove-member-from-card": "Eliminar de la tarjeta", + "remove-member-pop": "¿Eliminar __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero. En ellas se mostrará una notificación.", + "removeMemberPopup-title": "¿Eliminar miembro?", + "rename": "Renombrar", + "rename-board": "Renombrar el tablero", + "restore": "Restaurar", + "save": "Añadir", + "search": "Buscar", + "rules": "Reglas", + "search-cards": "Buscar entre los títulos, las descripciones de las tarjetas/listas y los campos personalizados en este tablero. ", + "search-example": "Write text you search and press Enter", + "select-color": "Seleccionar el color", + "select-board": "Seleccionar tablero", + "set-wip-limit-value": "Cambiar el límite para el número máximo de tareas en esta lista.", + "setWipLimitPopup-title": "Cambiar el límite del trabajo en proceso", + "shortcut-assign-self": "Asignarte a ti mismo a la tarjeta actual", + "shortcut-autocomplete-emoji": "Autocompletar emoji", + "shortcut-autocomplete-members": "Autocompletar miembros", + "shortcut-clear-filters": "Limpiar todos los filtros", + "shortcut-close-dialog": "Cerrar el cuadro de diálogo", + "shortcut-filter-my-cards": "Filtrar mis tarjetas", + "shortcut-show-shortcuts": "Mostrar esta lista de atajos", + "shortcut-toggle-filterbar": "Conmutar la barra lateral del filtro", + "shortcut-toggle-searchbar": "Conmutar la barra lateral de búsqueda", + "shortcut-toggle-sidebar": "Conmutar la barra lateral del tablero", + "show-cards-minimum-count": "Mostrar recuento de tarjetas si la lista contiene más de", + "sidebar-open": "Abrir la barra lateral", + "sidebar-close": "Cerrar la barra lateral", + "signupPopup-title": "Crear una cuenta", + "star-board-title": "Haz clic para destacar este tablero. Se mostrará en la parte superior de tu lista de tableros.", + "starred-boards": "Tableros destacados", + "starred-boards-description": "Los tableros destacados se mostrarán en la parte superior de tu lista de tableros.", + "subscribe": "Suscribirse", + "team": "Equipo", + "this-board": "este tablero", + "this-card": "esta tarjeta", + "spent-time-hours": "Tiempo consumido (horas)", + "overtime-hours": "Tiempo excesivo (horas)", + "overtime": "Tiempo excesivo", + "has-overtime-cards": "Hay tarjetas con el tiempo excedido", + "has-spenttime-cards": "Se ha excedido el tiempo de las tarjetas", + "time": "Hora", + "title": "Título", + "tracking": "Siguiendo", + "tracking-info": "Serás notificado de cualquier cambio en las tarjetas en las que participas como creador o miembro.", + "type": "Tipo", + "unassign-member": "Desvincular al miembro", + "unsaved-description": "Tienes una descripción por añadir.", + "unwatch": "Dejar de vigilar", + "upload": "Cargar", + "upload-avatar": "Cargar un avatar", + "uploaded-avatar": "Avatar cargado", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Nombre de usuario", + "import-usernames": "Importar Usuarios", + "view-it": "Verla", + "warn-list-archived": "advertencia: esta tarjeta está en una lista en el Archivo", + "watch": "Vigilar", + "watching": "Vigilando", + "watching-info": "Serás notificado de cualquier cambio en este tablero", + "welcome-board": "Tablero de bienvenida", + "welcome-swimlane": "Hito 1", + "welcome-list1": "Básicos", + "welcome-list2": "Avanzados", + "card-templates-swimlane": "Plantilla de tarjeta", + "list-templates-swimlane": "Listar plantillas", + "board-templates-swimlane": "Plantilla de tablero", + "what-to-do": "¿Qué quieres hacer?", + "wipLimitErrorPopup-title": "El límite del trabajo en proceso no es válido.", + "wipLimitErrorPopup-dialog-pt1": "El número de tareas en esta lista es mayor que el límite del trabajo en proceso que has definido.", + "wipLimitErrorPopup-dialog-pt2": "Por favor, mueve algunas tareas fuera de esta lista, o fija un límite del trabajo en proceso más alto.", + "admin-panel": "Panel del administrador", + "settings": "Preferencias", + "people": "Personas", + "registration": "Registro", + "disable-self-registration": "Deshabilitar autoregistro", + "invite": "Invitar", + "invite-people": "Invitar a personas", + "to-boards": "A el(los) tablero(s)", + "email-addresses": "Direcciones de correo electrónico", + "smtp-host-description": "Dirección del servidor SMTP para gestionar tus correos", + "smtp-port-description": "Puerto usado por el servidor SMTP para mandar correos", + "smtp-tls-description": "Habilitar el soporte TLS para el servidor SMTP", + "smtp-host": "Servidor SMTP", + "smtp-port": "Puerto SMTP", + "smtp-username": "Nombre de usuario", + "smtp-password": "Contraseña", + "smtp-tls": "Soporte TLS", + "send-from": "Desde", + "send-smtp-test": "Enviarte un correo de prueba a ti mismo", + "invitation-code": "Código de Invitación", + "email-invite-register-subject": "__inviter__ te ha enviado una invitación", + "email-invite-register-text": "Querido __user__,\n__inviter__ le invita al tablero kanban para colaborar.\n\nPor favor, siga el siguiente enlace:\n__url__\n\nY tu código de invitación es: __icode__\n\nGracias.", + "email-smtp-test-subject": "Prueba de email SMTP", + "email-smtp-test-text": "El correo se ha enviado correctamente", + "error-invitation-code-not-exist": "El código de invitación no existe", + "error-notAuthorized": "No estás autorizado a ver esta página.", + "webhook-title": "Nombre del Webhook", + "webhook-token": "Token (opcional para la autenticación)", + "outgoing-webhooks": "Webhooks salientes", + "bidirectional-webhooks": "Webhooks de doble sentido", + "outgoingWebhooksPopup-title": "Webhooks salientes", + "boardCardTitlePopup-title": "Filtro de títulos de tarjeta", + "disable-webhook": "Deshabilitar este Webhook", + "global-webhook": "Webhooks globales", + "new-outgoing-webhook": "Nuevo webhook saliente", + "no-name": "(Desconocido)", + "Node_version": "Versión de Node", + "Meteor_version": "Versión de Meteor", + "MongoDB_version": "Versión de MongoDB", + "MongoDB_storage_engine": "Motor de almacenamiento de MongoDB", + "MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado", + "OS_Arch": "Arquitectura del sistema", + "OS_Cpus": "Número de CPUs del sistema", + "OS_Freemem": "Memoria libre del sistema", + "OS_Loadavg": "Carga media del sistema", + "OS_Platform": "Plataforma del sistema", + "OS_Release": "Publicación del sistema", + "OS_Totalmem": "Memoria total del sistema", + "OS_Type": "Tipo de sistema", + "OS_Uptime": "Tiempo activo del sistema", + "days": "días", + "hours": "horas", + "minutes": "minutos", + "seconds": "segundos", + "show-field-on-card": "Mostrar este campo en la tarjeta", + "automatically-field-on-card": "Añadir campo a las tarjetas nuevas", + "always-field-on-card": "Añadir campo a todas las tarjetas", + "showLabel-field-on-card": "Mostrar etiquetas de campos en la minitarjeta.", + "yes": "Sí", + "no": "No", + "accounts": "Cuentas", + "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", + "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", + "createdAt": "Fecha de alta", + "modifiedAt": "Modified at", + "verified": "Verificado", + "active": "Activo", + "card-received": "Recibido", + "card-received-on": "Recibido el", + "card-end": "Finalizado", + "card-end-on": "Finalizado el", + "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", + "editCardEndDatePopup-title": "Cambiar la fecha de finalización", + "setCardColorPopup-title": "Cambiar el color", + "setCardActionsColorPopup-title": "Elegir un color", + "setSwimlaneColorPopup-title": "Elegir un color", + "setListColorPopup-title": "Elegir un color", + "assigned-by": "Asignado por", + "requested-by": "Solicitado por", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", + "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", + "boardDeletePopup-title": "¿Eliminar el tablero?", + "delete-board": "Eliminar el tablero", + "default-subtasks-board": "Subtareas para el tablero __board__", + "default": "Por defecto", + "queue": "Cola", + "subtask-settings": "Preferencias de las subtareas", + "card-settings": "Preferencias de la tarjeta", + "boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero", + "boardCardSettingsPopup-title": "Preferencias de la tarjeta", + "deposit-subtasks-board": "Depositar subtareas en este tablero:", + "deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:", + "show-parent-in-minicard": "Mostrar el padre en una minitarjeta:", + "prefix-with-full-path": "Prefijo con ruta completa", + "prefix-with-parent": "Prefijo con el padre", + "subtext-with-full-path": "Subtexto con ruta completa", + "subtext-with-parent": "Subtexto con el padre", + "change-card-parent": "Cambiar la tarjeta padre", + "parent-card": "Tarjeta padre", + "source-board": "Tablero de origen", + "no-parent": "No mostrar la tarjeta padre", + "activity-added-label": "añadida etiqueta %s a %s", + "activity-removed-label": "eliminada etiqueta '%s' desde %s", + "activity-delete-attach": "eliminado un adjunto desde %s", + "activity-added-label-card": "añadida etiqueta '%s'", + "activity-removed-label-card": "eliminada etiqueta '%s'", + "activity-delete-attach-card": "eliminado un adjunto", + "activity-set-customfield": "Cambiar el campo personalizado '%s' a '%s' en %s", + "activity-unset-customfield": "Desmarcar el campo personalizado '%s' en %s", + "r-rule": "Regla", + "r-add-trigger": "Añadir disparador", + "r-add-action": "Añadir acción", + "r-board-rules": "Reglas del tablero", + "r-add-rule": "Añadir regla", + "r-view-rule": "Ver regla", + "r-delete-rule": "Eliminar regla", + "r-new-rule-name": "Nueva título de regla", + "r-no-rules": "No hay reglas", + "r-trigger": "disparador", + "r-action": "acción", + "r-when-a-card": "Cuando una tarjeta", + "r-is": "es", + "r-is-moved": "es movida", + "r-added-to": "Añadido a", + "r-removed-from": "eliminado de", + "r-the-board": "el tablero", + "r-list": "la lista", + "list": "Lista", + "set-filter": "Filtrar", + "r-moved-to": "Movido a", + "r-moved-from": "Movido desde", + "r-archived": "Se archivó", + "r-unarchived": "Restaurado del archivo", + "r-a-card": "una tarjeta", + "r-when-a-label-is": "Cuando una etiqueta es", + "r-when-the-label": "Cuando la etiqueta es", + "r-list-name": "Nombre de lista", + "r-when-a-member": "Cuando un miembro es", + "r-when-the-member": "Cuando el miembro", + "r-name": "nombre", + "r-when-a-attach": "Cuando un adjunto", + "r-when-a-checklist": "Cuando una lista de verificación es", + "r-when-the-checklist": "Cuando la lista de verificación", + "r-completed": "Completada", + "r-made-incomplete": "Hecha incompleta", + "r-when-a-item": "Cuando un elemento de la lista de verificación es", + "r-when-the-item": "Cuando el elemento de la lista de verificación es", + "r-checked": "Marcado", + "r-unchecked": "Desmarcado", + "r-move-card-to": "Mover la tarjeta", + "r-top-of": "Arriba de", + "r-bottom-of": "Abajo de", + "r-its-list": "su lista", + "r-archive": "Archivar", + "r-unarchive": "Restaurar del Archivo", + "r-card": "la tarjeta", + "r-add": "Añadir", + "r-remove": "Eliminar", + "r-label": "etiqueta", + "r-member": "miembro", + "r-remove-all": "Eliminar todos los miembros de la tarjeta", + "r-set-color": "Cambiar el color a", + "r-checklist": "lista de verificación", + "r-check-all": "Marcar todo", + "r-uncheck-all": "Desmarcar todo", + "r-items-check": "elementos de la lista de verificación", + "r-check": "Marcar", + "r-uncheck": "Desmarcar", + "r-item": "elemento", + "r-of-checklist": "de la lista de verificación", + "r-send-email": "Enviar un email", + "r-to": "a", + "r-of": "de", + "r-subject": "asunto", + "r-rule-details": "Detalle de la regla", + "r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista", + "r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista", + "r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista", + "r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista", + "r-d-send-email": "Enviar email", + "r-d-send-email-to": "a", + "r-d-send-email-subject": "asunto", + "r-d-send-email-message": "mensaje", + "r-d-archive": "Archivar la tarjeta", + "r-d-unarchive": "Restaurar tarjeta del Archivo", + "r-d-add-label": "Añadir etiqueta", + "r-d-remove-label": "Eliminar etiqueta", + "r-create-card": "Crear una nueva tarjeta", + "r-in-list": "en la lista", + "r-in-swimlane": "en el carril", + "r-d-add-member": "Añadir miembro", + "r-d-remove-member": "Eliminar miembro", + "r-d-remove-all-member": "Eliminar todos los miembros", + "r-d-check-all": "Marcar todos los elementos de una lista", + "r-d-uncheck-all": "Desmarcar todos los elementos de una lista", + "r-d-check-one": "Marcar elemento", + "r-d-uncheck-one": "Desmarcar elemento", + "r-d-check-of-list": "de la lista de verificación", + "r-d-add-checklist": "Añadir una lista de verificación", + "r-d-remove-checklist": "Eliminar lista de verificación", + "r-by": "por", + "r-add-checklist": "Añadir una lista de verificación", + "r-with-items": "con items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Agregar el carril", + "r-swimlane-name": "nombre del carril", + "r-board-note": "Nota: deje un campo vacío para que coincida con todos los valores posibles", + "r-checklist-note": "Nota: los ítems de la lista tienen que escribirse como valores separados por coma.", + "r-when-a-card-is-moved": "Cuando una tarjeta es movida a otra lista", + "r-set": "Cambiar", + "r-update": "Actualizar", + "r-datefield": "campo de fecha", + "r-df-start-at": "comienza", + "r-df-due-at": "vencimiento", + "r-df-end-at": "finalizado", + "r-df-received-at": "recibido", + "r-to-current-datetime": "a la fecha/hora actual", + "r-remove-value-from": "Eliminar el valor de", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Método de autenticación", + "authentication-type": "Tipo de autenticación", + "custom-product-name": "Nombre de producto personalizado", + "layout": "Diseño", + "hide-logo": "Ocultar el logo", + "add-custom-html-after-body-start": "Añade HTML personalizado después de <body>", + "add-custom-html-before-body-end": "Añade HTML personalizado después de </body>", + "error-undefined": "Algo no está bien", + "error-ldap-login": "Ocurrió un error al intentar acceder", + "display-authentication-method": "Mostrar el método de autenticación", + "default-authentication-method": "Método de autenticación por defecto", + "duplicate-board": "Duplicar tablero", + "org-number": "El número de organizaciones es:", + "team-number": "El número de equipos es:", + "people-number": "El número de personas es:", + "swimlaneDeletePopup-title": "¿Eliminar el carril de flujo?", + "swimlane-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar el carril de flujo. Esta acción no puede deshacerse.", + "restore-all": "Restaurar todas", + "delete-all": "Borrar todas", + "loading": "Cargando. Por favor, espere.", + "previous_as": "el último tiempo fue", + "act-a-dueAt": "cambiada la hora de vencimiento a \nCuándo: __timeValue__\nDónde: __card__\n el vencimiento anterior fue __timeOldValue__", + "act-a-endAt": "cambiada la hora de finalización a __timeValue__ Fecha anterior: (__timeOldValue__)", + "act-a-startAt": "cambiada la hora de comienzo a __timeValue__ Fecha anterior: (__timeOldValue__)", + "act-a-receivedAt": "cambiada la fecha de recepción a __timeValue__ Fecha anterior: (__timeOldValue__)", + "a-dueAt": "cambiada la hora de vencimiento a", + "a-endAt": "cambiada la hora de finalización a", + "a-startAt": "cambiada la hora de comienzo a", + "a-receivedAt": "cambiada la hora de recepción a", + "almostdue": "está próxima la hora de vencimiento actual %s", + "pastdue": "se sobrepasó la hora de vencimiento actual%s", + "duenow": "la hora de vencimiento actual %s es hoy", + "act-newDue": "__list__/__card__ tiene una 1ra notificación de vencimiento [__board__]", + "act-withDue": "__list__/__card__ notificaciones de vencimiento [__board__]", + "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", + "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", + "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", + "act-atUserComment": "Se te mencionó en [__board__] __list__/__card__", + "delete-user-confirm-popup": "¿Seguro que quieres eliminar esta cuenta? Esta acción no puede deshacerse.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta", + "hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta", + "show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio", + "assignee": "Asignado", + "cardAssigneesPopup-title": "Asignado", + "addmore-detail": "Añadir una descripción detallada", + "show-on-card": "Mostrar en la tarjeta", + "new": "Nuevo", + "editOrgPopup-title": "Editar Organización", + "newOrgPopup-title": "Nueva Organización", + "editTeamPopup-title": "Editar Equipo", + "newTeamPopup-title": "Nuevo Equipo", + "editUserPopup-title": "Editar el usuario", + "newUserPopup-title": "Nuevo usuario", + "notifications": "Notificaciones", + "view-all": "Ver todo", + "filter-by-unread": "Filtrar por no leído", + "mark-all-as-read": "Marcar todo como leido", + "remove-all-read": "Eliminar todos los leídos", + "allow-rename": "Permitir renombrar", + "allowRenamePopup-title": "Permitir renombrar", + "start-day-of-week": "Establecer el dia de comienzo de la semana", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "status": "Estado", + "swimlane": "Carril", + "owner": "Propietario", + "last-modified-at": "Última modificación ", + "last-activity": "Última actividad", + "voting": "Votar", + "archived": "Archivado", + "delete-linked-card-before-this-card": "No puede borrar esta tarjeta antes de borrar la tarjeta enlazada que tiene", + "delete-linked-cards-before-this-list": "No puede borrar esta lista antes de borrar las tarjetas enlazadas que apuntan a tarjetas en esta lista", + "hide-checked-items": "Ocultar elementos marcados", + "task": "Tarea", + "create-task": "Create Task", + "ok": "Vale", + "organizations": "Organizaciones", + "teams": "Equipos", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Persona", + "my-cards": "Mis Tarjetas", + "card": "Tarjeta", + "board": "Tablero", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Yo", + "dueCardsViewChange-choice-all": "Todos los usuarios", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Tablero '%s' no encontrado.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "Lista '%s' no encontrada.", + "label-not-found": "Etiqueta '%s' no encontrada.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Usuario '%s' no encontrado.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Buscar todas las tarjetas", + "no-cards-found": "Ninguna tarjeta encontrada", + "one-card-found": "Una tarjeta encontrada", + "n-cards-found": "%s tarjetas encontradas", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "tablero", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "la lista", + "operator-list-abbrev": "l", + "operator-label": "etiqueta", + "operator-label-abbrev": "#", + "operator-user": "Usuario", + "operator-user-abbrev": "@", + "operator-member": "miembro", + "operator-member-abbrev": "m", + "operator-assignee": "Asignar", + "operator-assignee-abbrev": "a", + "operator-creator": "creador", + "operator-status": "estado", + "operator-due": "vencimiento", + "operator-created": "creado", + "operator-modified": "modificado", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "límite", + "predicate-archived": "archivado", + "predicate-open": "abierto", + "predicate-ended": "ended", + "predicate-all": "todo", + "predicate-overdue": "overdue", + "predicate-week": "semana", + "predicate-month": "mes", + "predicate-quarter": "cuarto", + "predicate-year": "año", + "predicate-due": "vencimiento", + "predicate-modified": "modificado", + "predicate-created": "creado", + "predicate-attachment": "adjunto", + "predicate-description": "descripción", + "predicate-checklist": "lista de verificación", + "predicate-start": "comienza", + "predicate-end": "finalizado", + "predicate-assignee": "Asignar", + "predicate-member": "miembro", + "predicate-public": "público", + "predicate-private": "privado", + "operator-unknown-error": "%s no es un operador", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' no es un estado válido", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s no es un límite válido. El límite ha de ser un entero positivo.", + "next-page": "Página Siguiente", + "previous-page": "Página Anterior", + "heading-notes": "Notas", + "globalSearch-instructions-heading": "Buscar instrucciones.", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Operadores disponibles:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Múltiple operadores pueden ser seleccionados.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "La búsqueda de texto distingue entre mayúsculas y minúsculas.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Enlazar a esta búsqueda", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Colores de las etiquetas", + "label-names": "Nombres de las etiquetas", + "archived-at": "archivado el", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Error del Servidor", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Título (Alfabéticamente)", + "created-at-newest-first": "Creación (Nuevos Primero)", + "created-at-oldest-first": "Creación (Antiguos Primero)", + "links-heading": "Enlaces", + "hide-system-messages-of-all-users": "Ocultar los mensajes de sistema de todos los usuarios", + "now-system-messages-of-all-users-are-hidden": "Los mensajes de sistema de todos los usuarios están ahora ocultos", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Pulsa intro para añadir más elementos", + "creator": "Creador", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/eu.i18n.json b/i18n/eu.i18n.json index fe92b534b..67ad0de5c 100644 --- a/i18n/eu.i18n.json +++ b/i18n/eu.i18n.json @@ -1,6 +1,6 @@ { "accept": "Onartu", - "act-activity-notify": "Activity Notification", + "act-activity-notify": "Jardueraren jakinarazpena", "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Gehitu eranskina", "add-board": "Gehitu arbela", + "add-template": "Add Template", "add-card": "Gehitu txartela", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Gehitu egiaztaketa zerrenda", @@ -113,6 +120,8 @@ "archives": "Artxibatu", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Esleitu kidea", "attached": "erantsita", "attachment": "Eranskina", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Ezabatu eranskina?", "attachments": "Eranskinak", "auto-watch": "Automatikoki jarraitu arbelak hauek sortzean", - "avatar-too-big": "Avatarra handiegia da (Gehienez 70Kb)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Atzera", "board-change-color": "Aldatu kolorea", "board-nb-stars": "%s izar", "board-not-found": "Ez da arbela aurkitu", "board-private-info": "Arbel hau <strong>pribatua</strong> izango da.", "board-public-info": "Arbel hau <strong>publikoa</strong> izango da.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Aldatu arbelaren atzealdea", "boardChangeTitlePopup-title": "Aldatu izena arbelari", "boardChangeVisibilityPopup-title": "Aldatu ikusgaitasuna", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Zerrendak", "bucket-example": "Esaterako \"Pertz zerrenda\"", "cancel": "Utzi", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Erantsi hemendik", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Ezabatu txartela?", "cardDetailsActionsPopup-title": "Txartel-ekintzak", "cardLabelsPopup-title": "Etiketak", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Txartelak", "cards-count": "Txartelak", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Itxi", "close-board": "Itxi arbela", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "beltza", "color-blue": "urdina", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "unekoa", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Data", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Arbel honetako kidea izan behar zara hori egin ahal izateko", "error-json-malformed": "Zure testua ez da baliozko JSON", "error-json-schema": "Zure JSON datuek ez dute formatu zuzenaren informazio egokia", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Zerrenda hau ez da existitzen", "error-user-doesNotExist": "Erabiltzaile hau ez da existitzen", "error-user-notAllowSelf": "Ezin duzu zure burua gonbidatu", "error-user-notCreated": "Erabiltzaile hau sortu gabe dago", "error-username-taken": "Erabiltzaile-izen hori hartuta dago", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "E-mail hori hartuta dago", "export-board": "Esportatu arbela", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Esportatu arbela", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Iragazi", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Garbitu iragazkia", + "filter-labels-label": "Filter by label", "filter-no-label": "Etiketarik ez", + "filter-member-label": "Filter by member", "filter-no-member": "Kiderik ez", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Iragazkia gaituta dago", "filter-on-desc": "Arbel honetako txartela iragazten ari zara. Egin klik hemen iragazkia editatzeko.", "filter-to-selection": "Iragazketa aukerara", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Izen abizenak", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Sortu arbela", "home": "Hasiera", "import": "Inportatu", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "inportatu arbela", "import-board-c": "Inportatu arbela", "import-board-title-trello": "Inportatu arbela Trellotik", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Inportatutako arbelak oraingo arbeleko informazio guztia ezabatuko du eta inportatutako arbeleko informazioarekin ordeztu.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Trellotik", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Isatsi baliozko JSON datuak hemen", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Kideen mapa", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Berrikusi kideen mapa", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Aukeratu zerrenda honetako txartel guztiak", "set-color-list": "Set Color", "listActionPopup-title": "Zerrendaren ekintzak", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Inportatu Trello txartel bat", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Gehiago", "link-list": "Lotura zerrenda honetara", "list-delete-pop": "Ekintza guztiak ekintza jariotik kenduko dira eta ezin izango duzu zerrenda berreskuratu. Ez dago desegiterik.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Eraman gora", "moveSelectionPopup-title": "Lekuz aldatu hautaketa", "multi-selection": "Hautaketa anitza", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Hautaketa anitza gaituta dago", "muted": "Mututua", "muted-info": "Ez zaizkizu jakinaraziko arbel honi egindako aldaketak", @@ -441,8 +525,9 @@ "search": "Bilatu", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Aukeratu kolorea", + "select-board": "Select Board", "set-wip-limit-value": "Zerrenda honetako atazen muga maximoa ezarri", "setWipLimitPopup-title": "WIP muga ezarri", "shortcut-assign-self": "Esleitu zure burua txartel honetara", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Iragazi nire txartelak", "shortcut-show-shortcuts": "Erakutsi lasterbideen zerrenda hau", "shortcut-toggle-filterbar": "Txandakatu iragazkiaren albo-barra", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Txandakatu arbelaren albo-barra", "show-cards-minimum-count": "Erakutsi txartel kopurua hau baino handiagoa denean:", "sidebar-open": "Ireki albo-barra", @@ -481,7 +567,15 @@ "upload": "Igo", "upload-avatar": "Igo avatar bat", "uploaded-avatar": "Avatar bat igo da", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Erabiltzaile-izena", + "import-usernames": "Import Usernames", "view-it": "Ikusi", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Ikuskatu", @@ -553,7 +647,8 @@ "minutes": "minutu", "seconds": "segundo", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Bai", "no": "Ez", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Baimendu e-mail aldaketa", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Noiz sortua", + "modifiedAt": "Modified at", "verified": "Egiaztatuta", "active": "Gaituta", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/fa-IR.i18n.json b/i18n/fa-IR.i18n.json new file mode 100644 index 000000000..7ff3da221 --- /dev/null +++ b/i18n/fa-IR.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "پذیرفتن", + "act-activity-notify": "اعلان فعالیت", + "act-addAttachment": "افزودن پیوست __attachment__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-deleteAttachment": "پاک کردن پیوست __attachment__ از کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addSubtask": "فزودن کار فرعی __subtask__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addLabel": "افزودن برچسب __label__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addedLabel": "برچسب اضافه شده __label__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-removeLabel": "برداشتن برچسب __label__ از کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-removedLabel": "برچسب برداشته شده __label__ از کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addChecklist": "افزودن چک لیست __checklist__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addChecklistItem": "آیتم اضافه شده به چک لیست __checklistItem__ در چک لیست __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-removeChecklist": "حذف چک لیست __checklist__ از کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-removeChecklistItem": "آیتم حذف شده از چک لیست __checklistItem__ در چک لیست __checkList__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-checkedItem": "بررسی شده __checklistItem__ از چک لیست __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-uncheckedItem": "بررسی نشده __checklistItem__ از چک لیست __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-completeChecklist": "چک لیست کامل شده __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-uncompleteChecklist": "چک لیست ناتمام __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-addComment": "افزودن نظر روی کارت __card__: __comment__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-editComment": "اصلاح نظر روی کارت __card__: __comment__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-deleteComment": "حذف نظر از کارت __card__: __comment__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-createBoard": "برد ساخته شده __board__", + "act-createSwimlane": "created swimlane __swimlane__ در برد __board__", + "act-createCard": "ایجاد کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-createCustomField": "ساخت فیلد اختصاصی __customField__ در برد __board__", + "act-deleteCustomField": "حذف فیلد اختصاصی __customField__ در برد __board__", + "act-setCustomField": "اصلاح فیلد اختصاصی __customField__: __customFieldValue__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-createList": "ایجاد لیست __list__ در برد __board__", + "act-addBoardMember": "افزودن عضو __member__ به برد __board__", + "act-archivedBoard": "برد __board__ به آرشیو منتقل شد", + "act-archivedCard": "کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__ به آرشیو منتقل شد", + "act-archivedList": "لیست __list__ at swimlane __swimlane__ در برد __board__ به آرشیو منتقل شد", + "act-archivedSwimlane": "Swimlane __swimlane__ در برد __board__ به آرشیو منتقل شد", + "act-importBoard": "وارد کردن برد __board__", + "act-importCard": "وارد کردن کارت __card__ به لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-importList": "وارد کردن لیست __list__ to swimlane __swimlane__ در برد __board__", + "act-joinMember": "عضو کردن __member__ به کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-moveCard": "انتقال کارت __card__ در برد __board__ از لیست __oldList__ at swimlane __oldSwimlane__ به لیست __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "انتقال کارت به برد دیگر __card__ از لیست __oldList__ at swimlane __oldSwimlane__ در برد __oldBoard__ به لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-removeBoardMember": "حذف عضویت __member__ از برد __board__", + "act-restoredCard": "کارت بازگردانی شده __card__ به لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-unjoinMember": "حذف عضو __member__ از کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "اقدامات", + "activities": "فعالیت ها", + "activity": "فعالیت", + "activity-added": "افزودن %s به %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ در کارت __card__ در لیست __list__ at swimlane __swimlane__ در برد __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/fa.i18n.json b/i18n/fa.i18n.json index f468c0489..b8b096be0 100644 --- a/i18n/fa.i18n.json +++ b/i18n/fa.i18n.json @@ -1,45 +1,45 @@ { - "accept": "پذیرش", + "accept": "تایید", "act-activity-notify": "اعلان فعالیت", - "act-addAttachment": "ضمیمه __attachment__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-deleteAttachment": "ضمیمه __attachment__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", - "act-addSubtask": "زیر وظیفه __subtask__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-addLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-addedLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-removeLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", - "act-removedLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", - "act-addChecklist": "سیاهه __checklist__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-addChecklistItem": "چک لیست __checklistItem__ را به سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-removeChecklist": "سیاهه __checklist__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", - "act-removeChecklistItem": "چک لیست __checklistItem__ را از سیاهه __checkList__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", - "act-checkedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ تیک زد", - "act-uncheckedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بدونِ تیک کرد", - "act-completeChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد", - "act-uncompleteChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ناقص کرد", - "act-addComment": "روی کارت __card__ نظر داد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__", - "act-editComment": "نظر روی کارت __card__ را ویرایش کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__", - "act-deleteComment": "نظر روی کارت __card__ را حذف کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__", - "act-createBoard": "برد __board__ را ایجاد کرد", - "act-createSwimlane": "مسیر شناور __swimlane__ را در برد __board__ ایجاد کرد", - "act-createCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ایجاد کرد", - "act-createCustomField": "فیلد شخصی __customField__ را در برد __board__ ایجاد کرد", - "act-deleteCustomField": "فیلد شخصی __customField__ را در برد __board__ حذف کرد", - "act-setCustomField": "فیلد شخصی __customField__ را ویرایش کرد: __customFieldValue__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__", - "act-createList": "لیست __list__ را به برد __board__ اضافه کرد", - "act-addBoardMember": "عضو __member__ را به برد __board__ اضافه کرد", - "act-archivedBoard": "برد __board__ را بایگانی کرد", - "act-archivedCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد", - "act-archivedList": "لیست __list__ را در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد", - "act-archivedSwimlane": "مسیر شناور __swimlane__ را در برد __board__ بایگانی کرد", - "act-importBoard": "برد __board__ را وارد کرد", - "act-importCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ وارد کرد", - "act-importList": "لیست __list__ را به مسیر شناور __swimlane__ در برد __board__ وارد کرد", - "act-joinMember": "عضو __member__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد", - "act-moveCard": "کارت __card__ را در برد __board__ از لیست __oldList__ در مسیر شناور __oldSwimlane__ به لیست __list__ در مسیر شناور __swimlane__ منتقل کرد", - "act-moveCardToOtherBoard": "کارت __card__ را از لیست __oldList__ در مسیر شناور __oldSwimlane__ در برد __oldBoard__ به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ منتقل کرد", + "act-addAttachment": "ضمیمه __attachment__ به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-deleteAttachment": "ضمیمه __attachment__ از کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف شده", + "act-addSubtask": "زیروظیفه __subtask__ به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-addLabel": "لیبل __label__ به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-addedLabel": "لیبل __label__ به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-removeLabel": "لیبل __label__ از کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف شده", + "act-removedLabel": "لیبل __label__ از کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف شده", + "act-addChecklist": "چک‌لیست __checklist__ به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-addChecklistItem": "آیتم چک‌لیست __checklistItem__ به چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه شده", + "act-removeChecklist": "چک‌لیست __checklist__ از کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف شده", + "act-removeChecklistItem": "آیتم چک‌لیست __checklistItem__ از چک‌لیست __checkList__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف شده", + "act-checkedItem": "آیتم چک‌لیست __checklistItem__ از چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ انتخاب‌شده", + "act-uncheckedItem": "آیتم چک‌لیست __checklistItem__ از چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ از حالت انتخاب خارج شده", + "act-completeChecklist": "چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ کامل شده", + "act-uncompleteChecklist": "چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ ناتمام‌شده", + "act-addComment": "روی کارت __card__ نظر داد: __comment__ در لیست __list__ در مسیر __swimlane__ در برد __board__", + "act-editComment": "نظر روی کارت __card__ ویرایش شده: __comment__ در لیست __list__ در مسیر __swimlane__ در برد __board__", + "act-deleteComment": "نظر روی کارت __card__ حذف‌شده: __comment__ در لیست __list__ در مسیر __swimlane__ در برد __board__", + "act-createBoard": "برد __board__ ایجاد شده", + "act-createSwimlane": "مسیر __swimlane__ در برد __board__ ایجاد شده", + "act-createCard": "کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ ایجاد شده", + "act-createCustomField": "فیلد سفارشی __customField__ در برد __board__ ایجاد شده", + "act-deleteCustomField": "فیلد سفارشی __customField__ در برد __board__ حذف شده", + "act-setCustomField": "فیلد سفارشی __customField__ ویرایش شده: __customFieldValue__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__", + "act-createList": "لیست __list__ به برد __board__ اضافه شده", + "act-addBoardMember": "عضو __member__ به برد __board__ اضافه شده", + "act-archivedBoard": "برد __board__ به آرشیو منتقل شد", + "act-archivedCard": "کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ آرشیو شده", + "act-archivedList": "لیست __list__ در مسیر __swimlane__ در برد __board__ آرشیو شده", + "act-archivedSwimlane": "مسیر __swimlane__ \\در برد __board__ آرشیو شده", + "act-importBoard": "برد __board__ وارد شده", + "act-importCard": "کارت __card__ را به لیست __list__ در مسیر __swimlane__ در برد __board__ وارد کرد", + "act-importList": "لیست __list__ را به مسیر __swimlane__ در برد __board__ وارد کرد", + "act-joinMember": "عضو __member__ را به کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ اضافه کرد", + "act-moveCard": "کارت __card__ را در برد __board__ از لیست __oldList__ در مسیر __oldSwimlane__ به لیست __list__ در مسیر __swimlane__ منتقل کرد", + "act-moveCardToOtherBoard": "کارت __card__ را از لیست __oldList__ در مسیر __oldSwimlane__ در برد __oldBoard__ به لیست __list__ در مسیر __swimlane__ در برد __board__ منتقل کرد", "act-removeBoardMember": "عضو __member__ را از برد __board__ حذف کرد", - "act-restoredCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بازگرداند", - "act-unjoinMember": "عضو __member__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد", + "act-restoredCard": "کارت __card__ را به لیست __list__ در مسیر __swimlane__ در برد __board__ بازگرداند", + "act-unjoinMember": "عضو __member__ را از کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ حذف کرد", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "اعمال", @@ -49,7 +49,7 @@ "activity-archived": "%s به آرشیو انتقال یافت", "activity-attached": "%s به %s پیوست شد", "activity-created": "%s ایجاد شد", - "activity-customfield-created": "%s فیلدشخصی ایجاد شد", + "activity-customfield-created": " فیلد سفارشی %s ایجاد شد", "activity-excluded": "%s از %s مستثنی گردید", "activity-imported": "%s از %s وارد %s شد", "activity-imported-board": "%s از %s وارد شد", @@ -60,38 +60,45 @@ "activity-sent": "ارسال %s به %s", "activity-unjoined": "قطع اتصال %s", "activity-subtask-added": "زیروظیفه به %s اضافه شد", - "activity-checked-item": "چک شده %s در چک لیست %s از %s", - "activity-unchecked-item": "چک نشده %s در چک لیست %s از %s", - "activity-checklist-added": "سیاهه به %s اضافه شد", - "activity-checklist-removed": "از چک لیست حذف گردید", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "تمام نشده ها در چک لیست %s از %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "حذف شده از چک لیست '%s' در %s", + "activity-checked-item": " %s مورد در چک‌لیست %s از %s انتخاب‌شده", + "activity-unchecked-item": " %s مورد در چک‌لیست %s از %s انتخاب‌نشده", + "activity-checklist-added": "چک‌لیست به %s اضافه شد", + "activity-checklist-removed": "یک چک‌لیست از %s حذف گردید", + "activity-checklist-completed": "چک‌لیست %s از %s کامل شده‌است", + "activity-checklist-uncompleted": "چک‌لیست %s از %s کامل نشده‌است", + "activity-checklist-item-added": "آیتم چک‌لیست به '%s' در %s اضافه شده", + "activity-checklist-item-removed": "یک آیتم چک‌لیست از '%s' در %s حذف شده.", "add": "افزودن", - "activity-checked-item-card": "چک شده %s در چک لیست %s", - "activity-unchecked-item-card": "چک نشده %s در چک لیست %s", - "activity-checklist-completed-card": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد", - "activity-checklist-uncompleted-card": "چک لیست تمام نشده %s", - "activity-editComment": "نظر ویرایش شد %s", - "activity-deleteComment": "نظر حذف شد %s", + "activity-checked-item-card": " %s در چک‌لیست %s انتخاب‌شده", + "activity-unchecked-item-card": "%s در چک‌لیست %s از حالت انتخاب خارج شده", + "activity-checklist-completed-card": "چک‌لیست __checklist__ در کارت __card__ در لیست __list__ در مسیر __swimlane__ در برد __board__ کامل شده", + "activity-checklist-uncompleted-card": "چک‌لیست %s تمام نشده ", + "activity-editComment": "%s نظر ویرایش شد ", + "activity-deleteComment": "%s نظر حذف شد ", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "افزودن ضمیمه", "add-board": "افزودن برد", + "add-template": "Add Template", "add-card": "افزودن کارت", - "add-swimlane": "اضافه کردن مسیر شناور", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "افزودن مسیر شنا", "add-subtask": "افزودن زیر وظیفه", - "add-checklist": "افزودن چک لیست", - "add-checklist-item": "افزودن مورد به سیاهه", - "add-cover": "جلد کردن", + "add-checklist": "افزودن چک‌لیست", + "add-checklist-item": "افزودن مورد به چک‌لیست", + "add-cover": "افزودن کاور", "add-label": "افزودن لیبل", "add-list": "افزودن لیست", "add-members": "افزودن اعضا", "added": "اضافه گردید", "addMemberPopup-title": "اعضا", "admin": "مدیر", - "admin-desc": "امکان دیدن و ویرایش کارت‌ها، پاک کردن کاربران و تغییر تنظیمات برای برد.", + "admin-desc": "امکان دیدن و ویرایش کارت‌ها، حذف اعضا و تغییرِ تنظیماتِ برد.", "admin-announcement": "اعلان", - "admin-announcement-active": "اعلان سراسری فعال", + "admin-announcement-active": "فعال کردن اعلان‌های سمت سیستم", "admin-announcement-title": "اعلان از سوی مدیر", "all-boards": "تمام بردها", "and-n-other-card": "و __count__ کارت دیگر", @@ -105,92 +112,131 @@ "archive-list": "انتقال لیست به آرشیو", "archive-swimlane": "انتقال مسیر به آرشیو", "archive-selection": "انتقال انتخاب شده ها به آرشیو", - "archiveBoardPopup-title": "انتقال برد به آرشیو؟", - "archived-items": "بایگانی", - "archived-boards": "برد های داخل آرشیو", + "archiveBoardPopup-title": "آیا برد به آرشیو منتقل شود؟", + "archived-items": "آرشیو", + "archived-boards": "بردهای داخل آرشیو", "restore-board": "بازیابی برد", - "no-archived-boards": "هیچ بردی داخل آرشیو نیست", - "archives": "بایگانی", - "template": "Template", - "templates": "Templates", - "assign-member": "تعیین عضو", + "no-archived-boards": "هیچ بردی داخل آرشیو نیست.", + "archives": "آرشیو", + "template": "قالب", + "templates": "قالب‌ها", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "انتصاب عضو", "attached": "ضمیمه شده", "attachment": "ضمیمه", "attachment-delete-pop": "حذف پیوست دایمی و بی بازگشت خواهد بود.", "attachmentDeletePopup-title": "آیا می خواهید ضمیمه را حذف کنید؟", "attachments": "ضمائم", "auto-watch": "اضافه شدن خودکار دیده‌بانی بردها زمانی که ایجاد می‌شوند", - "avatar-too-big": "تصویر کاربر بسیار بزرگ است ـ حداکثر۷۰ کیلوبایت ـ", + "avatar-too-big": "تصویر آواتار بسیار بزرگ است (حداکثر ۵۲۰ کیلوبایت)", "back": "بازگشت", "board-change-color": "تغییر رنگ", "board-nb-stars": "%s ستاره", "board-not-found": "برد مورد نظر پیدا نشد", "board-private-info": "این برد <strong>خصوصی</strong> خواهد بود.", "board-public-info": "این برد <strong>عمومی</strong> خواهد بود.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "تغییر پس زمینه برد", "boardChangeTitlePopup-title": "تغییر نام برد", "boardChangeVisibilityPopup-title": "تغییر وضعیت نمایش", - "boardChangeWatchPopup-title": "تغییر دیده بانی", - "boardMenuPopup-title": "Board Settings", + "boardChangeWatchPopup-title": "تغییر دیده‌بانی", + "boardMenuPopup-title": "تنظیمات برد", "boardChangeViewPopup-title": "نمایش برد", "boards": "بردها", "board-view": "نمایش برد", "board-view-cal": "تقویم", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "بستن", - "board-view-lists": "فهرست‌ها", - "bucket-example": "برای مثال چیزی شبیه \"لیست سبدها\"", + "board-view-swimlanes": "مسیرها", + "board-view-collapse": "جمع کردن", + "board-view-gantt": "گانت", + "board-view-lists": "لیست‌ها", + "bucket-example": "برای مثال چیزی شبیه \"لیست سطل\"", "cancel": "انصراف", - "card-archived": "این کارت به آرشیو انتقال داده شد", - "board-archived": "این برد به آرشیو انتقال یافت", + "card-archived": "این کارت به آرشیو انتقال داده‌شده‌است.", + "board-archived": "این برد به آرشیو انتقال داده‌شده‌است.", "card-comments-title": "این کارت دارای %s نظر است.", "card-delete-notice": "حذف دائمی. تمامی موارد مرتبط با این کارت از بین خواهند رفت.", "card-delete-pop": "همه اقدامات از این پردازه حذف خواهد شد و امکان بازگرداندن کارت وجود نخواهد داشت.", "card-delete-suggest-archive": "شما می توانید کارت را به بایگانی منتقل کنید تا آن را از هیئت مدیره حذف کنید و فعالیت را حفظ کنید.", - "card-due": "تا", - "card-due-on": "تا", + "card-due": "موعد", + "card-due-on": "موعد تا", "card-spent": "زمان صرف شده", "card-edit-attachments": "ویرایش ضمائم", - "card-edit-custom-fields": "ویرایش فیلدهای شخصی", - "card-edit-labels": "ویرایش برچسب", + "card-edit-custom-fields": "ویرایش فیلدهای سفارشی", + "card-edit-labels": "ویرایش لیبل‌ها", "card-edit-members": "ویرایش اعضا", - "card-labels-title": "تغییر برچسب کارت", - "card-members-title": "افزودن یا حذف اعضا از کارت.", + "card-labels-title": "لیبل‌های کارت را تغییر بده.", + "card-members-title": "اعضا ی برد را از/به کارت حذف/اضافه کنید.", "card-start": "شروع", "card-start-on": "شروع از", "cardAttachmentsPopup-title": "ضمیمه از", "cardCustomField-datePopup-title": "تغییر تاریخ", - "cardCustomFieldsPopup-title": "ویرایش فیلدهای شخصی", + "cardCustomFieldsPopup-title": "ویرایش فیلدهای سفارشی", + "cardStartVotingPopup-title": "شروع یک رای‌گیری", + "positiveVoteMembersPopup-title": "طرفداران", + "negativeVoteMembersPopup-title": "مخالفان", + "card-edit-voting": "ویرایش رای‌گیری", + "editVoteEndDatePopup-title": "تغییر تاریخ پایان رای‌گیری", + "allowNonBoardMembers": "اجازه دادن به همه کاربران وارد شده", + "vote-question": "سوال رای گیری", + "vote-public": "نمایش چه کسی به چه رای داده است", + "vote-for-it": "برای این", + "vote-against": "بر خلاف", + "deleteVotePopup-title": "رای‌گیری حذف شود؟", + "vote-delete-pop": "حذف کردن به صورت دائمی هست و قابل برگشت نیست. تمام اطلاعات مرتبط با این رای‌گیری حذف خواهدشد.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "آیا می خواهید کارت را حذف کنید؟", "cardDetailsActionsPopup-title": "اعمال کارت", - "cardLabelsPopup-title": "برچسب ها", + "cardLabelsPopup-title": "لیبل ها", "cardMembersPopup-title": "اعضا", "cardMorePopup-title": "بیشتر", - "cardTemplatePopup-title": "Create template", + "cardTemplatePopup-title": "ایجاد قالب", "cards": "کارت‌ها", "cards-count": "کارت‌ها", + "cards-count-one": "کارت", "casSignIn": "ورود با استفاده از CAS", "cardType-card": "کارت", - "cardType-linkedCard": "کارت‌های مرتبط", - "cardType-linkedBoard": "برد مرتبط", + "cardType-linkedCard": "کارت‌های لینک‌شده", + "cardType-linkedBoard": "برد لینک‌شده", "change": "تغییر", - "change-avatar": "تغییر تصویر", + "change-avatar": "تغییر آواتار", "change-password": "تغییر کلمه عبور", "change-permissions": "تغییر دسترسی‌ها", "change-settings": "تغییر تنظیمات", - "changeAvatarPopup-title": "تغییر تصویر", + "changeAvatarPopup-title": "تغییر آواتار", "changeLanguagePopup-title": "تغییر زبان", "changePasswordPopup-title": "تغییر کلمه عبور", "changePermissionsPopup-title": "تغییر دسترسی‌ها", "changeSettingsPopup-title": "تغییر تنظیمات", "subtasks": "زیر وظیفه", - "checklists": "سیاهه‌ها", + "checklists": "چک‌لیست‌ها", "click-to-star": "با کلیک کردن ستاره بدهید", "click-to-unstar": "با کلیک کردن ستاره را کم کنید", "clipboard": "ذخیره در حافظه ویا بردار-رهاکن", "close": "بستن", "close-board": "بستن برد", "close-board-pop": "شما می توانید با کلیک کردن بر روی دکمه «بایگانی» از صفحه هدر، صفحه را بازگردانید.", + "close-card": "Close Card", "color-black": "مشکی", "color-blue": "آبی", "color-crimson": "قرمز", @@ -224,26 +270,28 @@ "no-comments": "هیچ کامنتی موجود نیست", "no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.", "worker": "کارگر", - "worker-desc": "تنها میتوانید کارت ها را جابجا کنید، این را به یک کارت اضافه کنید.", + "worker-desc": "تنها می‌توانید کارت‌ها را جابجا کنید، آنها را به خود محول کنید و نظر دهید.", "computer": "رایانه", "confirm-subtask-delete-dialog": "از حذف این زیر وظیفه اطمینان دارید؟", - "confirm-checklist-delete-dialog": "مطمئنا چک لیست پاک شود؟", + "confirm-checklist-delete-dialog": "آیا مطمئنید می‌خواهید چک‌لیست را حذف کنید؟", "copy-card-link-to-clipboard": "درج پیوند کارت در حافظه", "linkCardPopup-title": "ارتباط دادن کارت", "searchElementPopup-title": "جستجو", "copyCardPopup-title": "کپی کارت", - "copyChecklistToManyCardsPopup-title": "کپی قالب کارت به کارت‌های متعدد", + "copyChecklistToManyCardsPopup-title": "کپی قالب چک‌لیست به کارت‌های متعدد", "copyChecklistToManyCardsPopup-instructions": "عنوان و توضیحات کارت مقصد در این قالب JSON", "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "ایجاد", "createBoardPopup-title": "ایجاد برد", "chooseBoardSourcePopup-title": "بارگذاری برد", - "createLabelPopup-title": "ایجاد برچسب", + "createLabelPopup-title": "ایجاد لیبل", "createCustomField": "ایجاد فیلد", "createCustomFieldPopup-title": "ایجاد فیلد", "current": "جاری", - "custom-field-delete-pop": "این اقدام فیلدشخصی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد", + "custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد", "custom-field-checkbox": "جعبه انتخابی", + "custom-field-currency": "واحد پولی", + "custom-field-currency-option": "کد واحد پولی", "custom-field-date": "تاریخ", "custom-field-dropdown": "لیست افتادنی", "custom-field-dropdown-none": "(هیچ)", @@ -255,18 +303,18 @@ "custom-fields": "فیلدهای شخصی", "date": "تاریخ", "decline": "رد", - "default-avatar": "تصویر پیش فرض", + "default-avatar": "آواتار پیش‌فرض", "delete": "حذف", - "deleteCustomFieldPopup-title": "آیا فیلدشخصی پاک شود؟", - "deleteLabelPopup-title": "آیا می خواهید برچسب را حذف کنید؟", + "deleteCustomFieldPopup-title": "آیا فیلد سفارشی پاک شود؟", + "deleteLabelPopup-title": "آیا می خواهید لیبل را حذف کنید؟", "description": "توضیحات", - "disambiguateMultiLabelPopup-title": "عمل ابهام زدایی از برچسب", + "disambiguateMultiLabelPopup-title": "عمل ابهام زدایی از لیبل", "disambiguateMultiMemberPopup-title": "عمل ابهام زدایی از کاربر", "discard": "لغو", "done": "انجام شده", "download": "دریافت", "edit": "ویرایش", - "edit-avatar": "تغییر تصویر", + "edit-avatar": "تغییر آواتار", "edit-profile": "ویرایش پروفایل", "edit-wip-limit": "Edit WIP Limit", "soft-wip-limit": "Soft WIP Limit", @@ -274,7 +322,7 @@ "editCardDueDatePopup-title": "تغییر تاریخ پایان", "editCustomFieldPopup-title": "ویرایش فیلد", "editCardSpentTimePopup-title": "تغییر زمان صرف شده", - "editLabelPopup-title": "تغیر برچسب", + "editLabelPopup-title": "تغیر لیبل", "editNotificationPopup-title": "اصلاح اعلان", "editProfilePopup-title": "ویرایش پروفایل", "email": "پست الکترونیک", @@ -295,15 +343,29 @@ "error-board-doesNotExist": "برد مورد نظر وجود ندارد", "error-board-notAdmin": "شما جهت انجام آن باید مدیر برد باشید", "error-board-notAMember": "شما برای انجام آن، باید عضو این برد باشید", - "error-json-malformed": "متن درغالب صحیح Json نمی باشد.", - "error-json-schema": "داده های Json شما، شامل اطلاعات صحیح در غالب درستی نمی باشد.", + "error-json-malformed": "متن در قالب صحیح Json نمی باشد.", + "error-json-schema": "داده‌های Json شما، شامل اطلاعات صحیح در قالب درستی نمی‌باشد.", + "error-csv-schema": "مقادیر موجود در فایل CSV (مقادیر جدا شده توسط کاما)/TSV (مقادیر جدا شده تب) در قالب درستی نمی‌باشد.", "error-list-doesNotExist": "این لیست موجود نیست", "error-user-doesNotExist": "این کاربر وجود ندارد", "error-user-notAllowSelf": "عدم امکان دعوت خود", "error-user-notCreated": "این کاربر ایجاد نشده است", "error-username-taken": "این نام کاربری استفاده شده است", + "error-orgname-taken": "نام سازمان پیشتر ثبت شده است", + "error-teamname-taken": "نام تیم پیشتر ثبت شده است", "error-email-taken": "رایانامه توسط گیرنده دریافت شده است", "export-board": "انتقال به بیرون برد", + "export-board-json": "اکسپورت برد به JSON", + "export-board-csv": "اکسپورت برد به CSV", + "export-board-tsv": "اکسپورت برد به TSV", + "export-board-excel": "خروجی برد به اکسل", + "user-can-not-export-excel": "کاربر قادر به گرفتن خروجی اکسلب نیست", + "export-board-html": "اکسپورت برد به HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "انتقال به بیرون برد", + "exportCardPopup-title": "Export card", "sort": "مرتب سازی", "sort-desc": "برای مرتب سازی لیست کلیک کنید", "list-sort-by": "مرتب سازی لیست بر اساس:", @@ -313,19 +375,31 @@ "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", - "filter": "صافی ـ فیلتر ـ", + "filter": "فیلتر", "filter-cards": "فیلتر کارت‌ها یا لیست‌ها", + "filter-dates-label": "فیلتر با تاریخ", + "filter-no-due-date": "بدون تاریخ مقرر", + "filter-overdue": "منقضی شده", + "filter-due-today": "مقتضی امروز", + "filter-due-this-week": "مقتضی این هفته", + "filter-due-tomorrow": "مقتضی فردا", "list-filter-label": "فیلتر لیست بر اساس عنوان", - "filter-clear": "حذف صافی ـ فیلتر ـ", - "filter-no-label": "بدون برچسب", + "filter-clear": "حذف فیلتر", + "filter-labels-label": "فیلتر کردن با لیبل", + "filter-no-label": "بدون لیبل", + "filter-member-label": "فیلتر کردن با عضو", "filter-no-member": "بدون عضو", - "filter-no-custom-fields": "هیچ فیلدشخصی ای وجود ندارد", + "filter-assignee-label": "فیلتر کردن با مسئول", + "filter-no-assignee": "منتصب‌نشده", + "filter-custom-fields-label": "فیلتر کردن با فیلدهای سفارشی", + "filter-no-custom-fields": "هیچ فیلد سفارشی ای وجود ندارد", "filter-show-archive": "نمایش لیست‌های آرشیو شده", "filter-hide-empty": "مخفی کردن لیست‌های خالی", - "filter-on": "صافی ـ فیلتر ـ فعال است", - "filter-on-desc": "شما درحال صافی ـ فیلتر ـ کارت‌های این برد هستید. برای ویرایش فیلتر کلیک نمایید.", - "filter-to-selection": "صافی ـ فیلتر ـ برای موارد انتخابی", - "advanced-filter-label": "صافی ـ فیلتر ـ پیشرفته", + "filter-on": "فیلتر فعال است", + "filter-on-desc": "شما درحال فیلتر کارت‌های این برد هستید. برای ویرایش فیلتر کلیک نمایید.", + "filter-to-selection": "فیلتر برای موارد انتخابی", + "other-filters-label": "فیلترهای دیگر", + "advanced-filter-label": "فیلتر پیشرفته", "advanced-filter-description": "فیلتر پیشرفته اجازه می دهد تا برای نوشتن رشته حاوی اپراتورهای زیر: ==! = <=> = && || () یک فضای به عنوان یک جداساز بین اپراتورها استفاده می شود. با تایپ کردن نام ها و مقادیر آنها می توانید برای تمام زمینه های سفارشی فیلتر کنید. به عنوان مثال: Field1 == Value1. نکته: اگر فیلدها یا مقادیر حاوی فضاها باشند، شما باید آنها را به یک نقل قول کپسول کنید. برای مثال: 'فیلد 1' == 'مقدار 1'. برای تک تک کاراکترهای کنترل (\\\\) که می توانید از آنها استفاده کنید، می توانید از \\ استفاده کنید. به عنوان مثال: Field1 == I \\ 'm. همچنین شما می توانید شرایط مختلف را ترکیب کنید. برای مثال: F1 == V1 || F1 == V2. به طور معمول همه اپراتورها از چپ به راست تفسیر می شوند. شما می توانید سفارش را با قرار دادن براکت تغییر دهید. برای مثال: F1 == V1 && (F2 == V2 || F2 == V3). همچنین می توانید فیلدهای متنی را با استفاده از regex جستجو کنید: F1 == /Tes.*/i", "fullname": "نام و نام خانوادگی", "header-logo-title": "بازگشت به صفحه بردها.", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "ایجاد برد", "home": "خانه", "import": "وارد کردن", + "impersonate-user": "جعل هویت کاربر", "link": "ارتباط", "import-board": "وارد کردن برد", "import-board-c": "وارد کردن برد", "import-board-title-trello": "وارد کردن برد از Trello", "import-board-title-wekan": "بارگذاری برد ها از آخرین خروجی", - "import-sandstorm-backup-warning": "قبل از بررسی این داده ها را از صفحه اصلی صادر شده یا Trello وارد نمیکنید این دانه دوباره باز می شود و یا دوباره باز می شود، یا برد را پیدا نمی کنید، این بدان معنی است که از دست دادن اطلاعات.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "وارد کردن برد از CSV/TSV", "from-trello": "از Trello", "from-wekan": "از آخرین خروجی", + "from-csv": "از CSV/TSV", "import-board-instruction-trello": "در Trello-ی خود به 'Menu'، 'More'، 'Print'، 'Export to JSON رفته و متن نهایی را دراینجا وارد نمایید.", + "import-board-instruction-csv": "مقادیر جداشده با کاما (CSV)/مقادیر جداشده با تبِ (TSV) خود را پیست کنید.", "import-board-instruction-wekan": "در هیئت مدیره خود، به 'Menu' بروید، سپس 'Export Board'، و متن را در فایل دانلود شده کپی کنید.", "import-board-instruction-about-errors": "اگر هنگام بازگردانی با خطا مواجه شدید بعضی اوقات بازگردانی انجام می شود و تمامی برد ها در داخل صفحه All Boards هستند", "import-json-placeholder": "اطلاعات Json معتبر خود را اینجا وارد کنید.", + "import-csv-placeholder": "اطلاعات CSV/TSV خود را اینجا پیست کنید.", "import-map-members": "نگاشت اعضا", "import-members-map": "برد ها بازگردانده شده تعدادی کاربر دارند . لطفا کاربر های که می خواهید را انتخاب نمایید", + "import-members-map-note": "نکته: اعضا بدون نقشه به کاربر فعلی واگذار می شوند", "import-show-user-mapping": "بررسی نقشه کاربران", "import-user-select": "کاربر فعلی خود را انتخاب نمایید اگر میخواهیپ بعنوان کاربر باشد", "importMapMembersAddPopup-title": "انتخاب کاربر", @@ -355,14 +433,14 @@ "initials": "تخصیصات اولیه", "invalid-date": "تاریخ نامعتبر", "invalid-time": "زمان نامعتبر", - "invalid-user": "کاربر نامعتیر", + "invalid-user": "کاربر نامعتبر", "joined": "متصل", "just-invited": "هم اکنون، شما به این برد دعوت شده‌اید.", "keyboard-shortcuts": "میانبر کلیدها", - "label-create": "ایجاد برچسب", - "label-default": "%s برچسب(پیش فرض)", - "label-delete-pop": "این اقدام، برچسب را از همه کارت‌ها پاک خواهد کرد و تاریخچه آن را نیز از بین می‌برد.", - "labels": "برچسب ها", + "label-create": "ایجاد لیبل", + "label-default": "%s لیبل(پیش فرض)", + "label-delete-pop": "این اقدام، لیبل را از همه کارت‌ها پاک خواهد کرد و تاریخچه آن را نیز از بین می‌برد.", + "labels": "لیبل ها", "language": "زبان", "last-admin-desc": "شما نمی توانید نقش ـroleـ را تغییر دهید چراکه باید حداقل یک مدیری وجود داشته باشد.", "leave-board": "خروج از برد", @@ -375,9 +453,13 @@ "list-select-cards": "انتخاب تمام کارت های این لیست", "set-color-list": "انتخاب رنگ", "listActionPopup-title": "لیست اقدامات", + "settingsUserPopup-title": "تنظیمات کاربر", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "اضافه کردن مسیر شناور", + "swimlaneAddPopup-title": "اضافه کردن مسیر", "listImportCardPopup-title": "وارد کردن کارت Trello", + "listImportCardsTsvPopup-title": "وارد کردن CSV/TSV اکسل", "listMorePopup-title": "بیشتر", "link-list": "پیوند به این فهرست", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "انتقال به بالا", "moveSelectionPopup-title": "حرکت مورد انتخابی", "multi-selection": "امکان چند انتخابی", + "multi-selection-label": "تغییر لیبل انتخاب‌شده‌ها", + "multi-selection-member": "تغییر عضو برای انتخاب‌شده‌ها", "multi-selection-on": "حالت چند انتخابی روشن است", "muted": "بی صدا", "muted-info": "شما هیچگاه از تغییرات این برد مطلع نخواهید شد", @@ -428,7 +512,7 @@ "quick-access-description": "جهت افزودن یک برد به اینجا، آن را ستاره دار نمایید.", "remove-cover": "حذف کاور", "remove-from-board": "حذف از برد", - "remove-label": "حذف برچسب", + "remove-label": "حذف لیبل", "listDeletePopup-title": "حذف فهرست؟", "remove-member": "حذف عضو", "remove-member-from-card": "حذف از کارت", @@ -439,20 +523,22 @@ "restore": "بازیابی", "save": "ذخیره", "search": "جستجو", - "rules": "قوانین", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "متن مورد جستجو؟", + "rules": "قواعد", + "search-cards": "جستجو در عناوین، توضیحات و فیلدهای سفارشیِ کارت‌ها/لیست‌ها در این برد", + "search-example": "کلمه مورد جستجو را وارد و اینتر را بزنید", "select-color": "انتخاب رنگ", + "select-board": "انتخاب برد", "set-wip-limit-value": "تعیین بیشینه تعداد وظایف در این فهرست", "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "اختصاص خود به کارت فعلی", + "shortcut-assign-self": "انتصاب خود به کارت فعلی", "shortcut-autocomplete-emoji": "تکمیل خودکار شکلکها", "shortcut-autocomplete-members": "تکمیل خودکار کاربرها", "shortcut-clear-filters": "حذف تمامی صافی‌ها ـ فیلترها ـ", "shortcut-close-dialog": "بستن محاوره", "shortcut-filter-my-cards": "کارت های من", "shortcut-show-shortcuts": "بالا آوردن میانبر این لیست", - "shortcut-toggle-filterbar": "ضامن نوار جداکننده صافی ـ فیلتر ـ", + "shortcut-toggle-filterbar": "ضامن نوار جداکننده فیلتر", + "shortcut-toggle-searchbar": "کلید نوار جستجوی جانبی", "shortcut-toggle-sidebar": "ضامن نوار جداکننده برد", "show-cards-minimum-count": "نمایش تعداد کارتها اگر لیست شامل بیشتراز", "sidebar-open": "بازکردن جداکننده", @@ -477,25 +563,33 @@ "type": "نوع", "unassign-member": "عدم انتصاب کاربر", "unsaved-description": "شما توضیحات ذخیره نشده دارید.", - "unwatch": "عدم دیده بانی", - "upload": "ارسال", - "upload-avatar": "ارسال تصویر", - "uploaded-avatar": "تصویر ارسال شد", + "unwatch": "عدم دیده‌بانی", + "upload": "آپلود", + "upload-avatar": "ارسال آواتار", + "uploaded-avatar": "آواتار آپلود شد", + "custom-top-left-corner-logo-image-url": "آدرس تصویر لوگوی سفارشی در گوشه چپ و بالا", + "custom-top-left-corner-logo-link-url": "آدرس لینک لوگوی سفارشی در گوشه چپ و بالا", + "custom-top-left-corner-logo-height": "ارتفاع لوگوی سفارشی در گوشه چپ و بالا. پیش‌فرض: ۲۷", + "custom-login-logo-image-url": "آدرس تصویر لوگوی سفارشی در لاگین", + "custom-login-logo-link-url": "آدرس لینک لوگوی سفارشی در لاگین", + "text-below-custom-login-logo": "متن پایین لوگوی سفارشی در فرم لاگین", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "نام کاربری", + "import-usernames": "درون ریزی کاربران", "view-it": "مشاهده", - "warn-list-archived": "اخطار:این کارت در یک لیست در آرشیو موجود می باشد", - "watch": "دیده بانی", - "watching": "درحال دیده بانی", + "warn-list-archived": "اخطار: این کارت در یک لیست در آرشیو موجود می‌باشد", + "watch": "دیده‌بانی", + "watching": "درحال دیده‌بانی", "watching-info": "شما از هر تغییری در این برد آگاه خواهید شد", - "welcome-board": "به این برد خوش آمدید", - "welcome-swimlane": "Milestone 1", + "welcome-board": "برد خوش‌آمدگویی", + "welcome-swimlane": "نقطع عطف 1", "welcome-list1": "پایه ای ها", "welcome-list2": "پیشرفته", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", + "card-templates-swimlane": "قالب‌های کارت", + "list-templates-swimlane": "لیست قالب‌ها", + "board-templates-swimlane": "قالب‌های برد", "what-to-do": "چه کاری می خواهید انجام دهید؟", - "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-title": "محدودی نامعتبر WIP", "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", "admin-panel": "پیشخوان مدیریتی", @@ -514,7 +608,7 @@ "smtp-port": "شماره درگاه ـPortـ سرور SMTP", "smtp-username": "نام کاربری", "smtp-password": "کلمه عبور", - "smtp-tls": "پشتیبانی از SMTP", + "smtp-tls": "پشتیبانی از TLS", "send-from": "از", "send-smtp-test": "فرستادن رایانامه آزمایشی به خود", "invitation-code": "کد دعوت نامه", @@ -525,42 +619,44 @@ "error-invitation-code-not-exist": "چنین کد دعوتی یافت نشد", "error-notAuthorized": "شما مجاز به دیدن این صفحه نیستید.", "webhook-title": "نام وب‌هوک", - "webhook-token": "توکن", + "webhook-token": "توکن (انتخابی برای اعتبارسنجی)", "outgoing-webhooks": "Outgoing Webhooks", "bidirectional-webhooks": "وب‌هوک two-way", "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "فیلتر موضوع کارت", + "boardCardTitlePopup-title": "فیلتر عنوان کارت", "disable-webhook": "حذف این وب‌هوک", "global-webhook": "وب‌هوک‌های سراسری", "new-outgoing-webhook": "New Outgoing Webhook", "no-name": "(ناشناخته)", "Node_version": "نسخه Node", - "Meteor_version": "Meteor version", + "Meteor_version": "نسخه متئور", "MongoDB_version": "ورژن MongoDB", "MongoDB_storage_engine": "موتور ذخیره سازی MongoDB", - "MongoDB_Oplog_enabled": "MongoDB Oplog فعال", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "روزه‌ها", + "MongoDB_Oplog_enabled": "MongoDB Oplog فعال است", + "OS_Arch": "معماری سیستم‌عامل", + "OS_Cpus": "تعدا سی‌پی‌یو سیستم‌عامل", + "OS_Freemem": "حافظه سیستم‌عامل", + "OS_Loadavg": "میانگین لود سیستم‌عامل", + "OS_Platform": "پلتفرم سیستم‌عامل", + "OS_Release": "انتشار سیستم‌عامل", + "OS_Totalmem": "حافظه کل سیستم‌عامل", + "OS_Type": "نوع سیستم‌عامل", + "OS_Uptime": "آپ‌تایم سیستم‌عامل", + "days": "روزها", "hours": "ساعت", "minutes": "دقیقه", "seconds": "ثانیه", - "show-field-on-card": "این رشته را در کارت نمایش بده", - "automatically-field-on-card": "اتوماتیک این رشته را در همه ی کارت ها اضافه کن", - "showLabel-field-on-card": "نمایش نام رشته در کارت های کوچک", + "show-field-on-card": "این فیلد را در کارت نمایش بده", + "automatically-field-on-card": "افزودن فیلد به کارت های جدید", + "always-field-on-card": "افزودن فیلد به همه کارت ها", + "showLabel-field-on-card": "نمایش لیبل فیلد در کارت‌های کوچک", "yes": "بله", "no": "خیر", "accounts": "حساب‌ها", - "accounts-allowEmailChange": "اجازه تغییر رایانامه", + "accounts-allowEmailChange": "اجازه تغییر ایمیل", "accounts-allowUserNameChange": "اجازه تغییر نام کاربری", "createdAt": "ساخته شده در", + "modifiedAt": "تغییر یافته در", "verified": "معتبر", "active": "فعال", "card-received": "رسیده", @@ -575,17 +671,18 @@ "setListColorPopup-title": "انتخاب کردن رنگ", "assigned-by": "محول شده توسط", "requested-by": "تقاضا شده توسط", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "حذف دائمی است شما تمام لیست ها، کارت ها و اقدامات مرتبط با این برد را از دست خواهید داد.", - "delete-board-confirm-popup": "تمام لیست ها، کارت ها، برچسب ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد", + "delete-board-confirm-popup": "تمام لیست ها، کارت ها، لیبل ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد", "boardDeletePopup-title": "حذف برد؟", "delete-board": "حذف برد", - "default-subtasks-board": "ریزکار برای __board__ برد", + "default-subtasks-board": "زیروظایفِ برد __board__", "default": "پیش‌فرض", "queue": "صف", - "subtask-settings": "تنظیمات ریزکارها", - "card-settings": "Card Settings", - "boardSubtaskSettingsPopup-title": "تنظیمات ریزکار های برد", - "boardCardSettingsPopup-title": "Card Settings", + "subtask-settings": "تنظیمات زیروظایف", + "card-settings": "تنظیمات کارت", + "boardSubtaskSettingsPopup-title": "تنظیمات زیروظایف برد", + "boardCardSettingsPopup-title": "تنظیمات کارت", "deposit-subtasks-board": "افزودن ریزکار به برد:", "deposit-subtasks-list": "لیست برای ریزکار های افزوده شده", "show-parent-in-minicard": "نمایش خانواده در ریز کارت", @@ -594,33 +691,36 @@ "subtext-with-full-path": "زیرنویس با مسیر کامل", "subtext-with-parent": "زیرنویس با خانواده", "change-card-parent": "تغییرخانواده کارت", - "parent-card": "کارت خانواده", - "source-board": "کارت مرجع", - "no-parent": "خانواده نمایش داده نشود", + "parent-card": "کارت پدر", + "source-board": "برد مرجع", + "no-parent": "پدر را نمایش نده", "activity-added-label": "افزودن لیبل '%s' به %s", "activity-removed-label": "حذف لیبل '%s' از %s", "activity-delete-attach": "حذف ضمیمه از %s", - "activity-added-label-card": "افزودن لیبل '%s'", - "activity-removed-label-card": "حذف لیبل '%s'", + "activity-added-label-card": "لیبل افزوده‌شده '%s'", + "activity-removed-label-card": "لیبل حذف‌شده '%s'", "activity-delete-attach-card": "حذف ضمیمه", "activity-set-customfield": "set custom field '%s' to '%s' in %s", "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "نقش", + "r-rule": "قاعده", "r-add-trigger": "افزودن گیره", "r-add-action": "افزودن عملیات", - "r-board-rules": "قوانین برد", - "r-add-rule": "افزودن نقش", - "r-view-rule": "نمایش قانون", - "r-delete-rule": "حذف قانون", - "r-new-rule-name": "تیتر قانون جدید", - "r-no-rules": "بدون قانون", + "r-board-rules": "قواعد برد", + "r-add-rule": "افزودن قاعده", + "r-view-rule": "نمایش قاعده", + "r-delete-rule": "حذف قاعده", + "r-new-rule-name": "تیتر قاعده جدید", + "r-no-rules": "بدون قواعد", + "r-trigger": "تریگر", + "r-action": "عملیات", "r-when-a-card": "زمانی که کارت", "r-is": "هست", "r-is-moved": "جابه‌جا شده", - "r-added-to": "اضافه شد به", + "r-added-to": "افزوده‌شده به", "r-removed-from": "حذف از", "r-the-board": "برد", "r-list": "لیست", + "list": "لیست", "set-filter": "اضافه کردن فیلتر", "r-moved-to": "انتقال به", "r-moved-from": "انتقال از", @@ -634,12 +734,12 @@ "r-when-the-member": "زمانی که کاربر", "r-name": "نام", "r-when-a-attach": "زمانی که ضمیمه", - "r-when-a-checklist": "زمانی که چک لیست هست", - "r-when-the-checklist": "زمانی که چک لیست", + "r-when-a-checklist": "زمانی که چک‌لیست هست", + "r-when-the-checklist": "زمانی که چک‌لیست", "r-completed": "تمام شده", "r-made-incomplete": "تمام نشده", - "r-when-a-item": "زمانی که چک لیست ایتم هست", - "r-when-the-item": "زمانی که چک لیست ایتم", + "r-when-a-item": "زمانی که آیتم چک‌لیست هست", + "r-when-the-item": "زمانی که آیتم چک‌لیست", "r-checked": "انتخاب شده", "r-unchecked": "لغو انتخاب", "r-move-card-to": "انتقال کارت به", @@ -651,22 +751,23 @@ "r-card": "کارت", "r-add": "افزودن", "r-remove": "حذف", - "r-label": "برچسب", + "r-label": "لیبل", "r-member": "عضو", "r-remove-all": "حذف همه کاربران از کارت", "r-set-color": "انتخاب رنگ به", - "r-checklist": "چک لیست", + "r-checklist": "چک‌لیست", "r-check-all": "انتخاب همه", "r-uncheck-all": "لغو انتخاب همه", - "r-items-check": "آیتم از چک لیست", + "r-items-check": "آیتم‌های چک‌لیست", "r-check": "انتخاب", "r-uncheck": "لغو انتخاب", "r-item": "آیتم", - "r-of-checklist": "از چک لیست", + "r-of-checklist": "از چک‌لیست", "r-send-email": "ارسال ایمیل", "r-to": "به", + "r-of": "از", "r-subject": "عنوان", - "r-rule-details": "جزئیات قوانین", + "r-rule-details": "جزئیات قاعده", "r-d-move-to-top-gen": "انتقال کارت به ابتدای لیست خود", "r-d-move-to-top-spec": "انتقال کارت به ابتدای لیست", "r-d-move-to-bottom-gen": "انتقال کارت به انتهای لیست خود", @@ -677,11 +778,11 @@ "r-d-send-email-message": "پیام", "r-d-archive": "انتقال کارت به آرشیو", "r-d-unarchive": "بازگردانی کارت از آرشیو", - "r-d-add-label": "افزودن برچسب", - "r-d-remove-label": "حذف برچسب", + "r-d-add-label": "افزودن لیبل", + "r-d-remove-label": "حذف لیبل", "r-create-card": "ساخت کارت جدید", "r-in-list": "در لیست", - "r-in-swimlane": "در مسیرِ شناور", + "r-in-swimlane": "در مسیرِ", "r-d-add-member": "افزودن عضو", "r-d-remove-member": "حذف عضو", "r-d-remove-all-member": "حذف تمامی کاربران", @@ -689,19 +790,19 @@ "r-d-uncheck-all": "لغوانتخاب تمام آیتم های لیست", "r-d-check-one": "انتخاب آیتم", "r-d-uncheck-one": "لغو انتخاب آیتم", - "r-d-check-of-list": "از چک لیست", - "r-d-add-checklist": "افزودن چک لیست", - "r-d-remove-checklist": "حذف چک لیست", + "r-d-check-of-list": "از چک‌لیست", + "r-d-add-checklist": "افزودن چک‌لیست", + "r-d-remove-checklist": "حذف چک‌لیست", "r-by": "توسط", - "r-add-checklist": "افزودن چک لیست", - "r-with-items": "با موارد", - "r-items-list": "مورد۱،مورد۲،مورد۳", - "r-add-swimlane": "اضافه کردن مسیر شناور", - "r-swimlane-name": "نام مسیر شناور", + "r-add-checklist": "افزودن چک‌لیست", + "r-with-items": "با آیتم‌های", + "r-items-list": "آیتم1،آیتم2،آیتم3", + "r-add-swimlane": "افزودن مسیر", + "r-swimlane-name": "نام مسیر", "r-board-note": "نکته: برای نمایش موارد ممکن کادر را خالی بگذارید.", "r-checklist-note": "نکته: چک‌لیست‌ها باید توسط کاما از یک‌دیگر جدا شوند.", "r-when-a-card-is-moved": "زمانی که یک کارت به لیست دیگری منتقل شد", - "r-set": "Set", + "r-set": "تنظیم", "r-update": "به روز رسانی", "r-datefield": "تاریخ", "r-df-start-at": "شروع", @@ -724,46 +825,238 @@ "error-ldap-login": "هنگام تلاش برای ورود به یک خطا رخ داد", "display-authentication-method": "نمایش نوع اعتبارسنجی", "default-authentication-method": "نوع اعتبارسنجی پیشفرض", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", + "duplicate-board": "برد تکراری", + "org-number": "تعداد سازمان ها", + "team-number": "تعداد تیم ها", + "people-number": "تعدا افراد:", + "swimlaneDeletePopup-title": "مسیر حذف شود؟", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", + "restore-all": "بازگردانی همه", + "delete-all": "حذف همه", + "loading": "در حال بارگزاری، لطفاً منتظر بمانید.", "previous_as": "آخرین زمان بوده", "act-a-dueAt": "اصلاح زمان انجام به \nکِی: __timeValue__\nکجا: __card__\n زمان قبلی انجام __timeOldValue__ بوده", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "act-a-endAt": "زمان پایان ویرایش‌شده به __timeValue__ از (__timeOldValue__)", + "act-a-startAt": "زمان آغاز ویرایش‌شده به __timeValue__ از (__timeOldValue__)", + "act-a-receivedAt": "زمان رسیدن ویرایش شده به __timeValue__ از (__timeOldValue__)", + "a-dueAt": "زمان سررسید ویرایش‌شده به", + "a-endAt": "زمان پایان ویرایش‌شده به", + "a-startAt": "زمان شروع ویرایش‌شده به", + "a-receivedAt": "زمان رسیدن ویرایش شده به", + "almostdue": "زمان سررسید فعلی %s د رحال رسیدن است", + "pastdue": "زمان سررسید فعلی %s گذشته‌است", + "duenow": "زمان سررسید فعلی %s امروز است", + "act-newDue": "__list__/__card__ اولین یادآوری سررسید در برد [__board__] را دارد", + "act-withDue": "__list__/__card__ یادآوری‌های سررسید [__board__]", + "act-almostdue": "یاآوری سررسید (__timeValue__) از __card__ در حال رسیدن است", + "act-pastdue": "یاآوری سررسید (__timeValue__) از __card__ گذشته‌است", + "act-duenow": "یاآوری سررسید (__timeValue__) از __card__ هم‌اکنون است", + "act-atUserComment": "به شما در [__board__] __list__/__card__ اشاره‌شده‌است.", + "delete-user-confirm-popup": "از حذف این اکانت مطمئن هستید؟ این عملیات برگشت‌ناپذیر است.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "به کاربران اجازه دهید خودشان اکانتشان را حذف کنند", + "hide-minicard-label-text": "مخفی کردن اسم لیبل مینی کارت", + "show-desktop-drag-handles": "نمایش دستگیره‌های درگ‌کردن دسکتاپ", + "assignee": "محول شده", + "cardAssigneesPopup-title": "محول‌شده", + "addmore-detail": "افزودن توضیحات کامل تر", + "show-on-card": "نمایش در کارت", + "new": "جدید", + "editOrgPopup-title": "ویرایش سازمان", + "newOrgPopup-title": "سازمان جدید", + "editTeamPopup-title": "ویرایش تیم", + "newTeamPopup-title": "تیم جدید", + "editUserPopup-title": "ویرایش کاربر", + "newUserPopup-title": "کاربر جدید", + "notifications": "اعلان‌ها", + "view-all": "مشاهده همه", + "filter-by-unread": "فیلتر با خوانده نشده", + "mark-all-as-read": "علامت همه به خوانده شده", + "remove-all-read": "حذف همه خوانده شده", + "allow-rename": "اجازه تغییر نام", + "allowRenamePopup-title": "اجازه تغییر نام", + "start-day-of-week": "تنظیم روز شروع هفته", + "monday": "دوشنبه", + "tuesday": "سه شنبه", + "wednesday": "چهارشنبه", + "thursday": "پنجشنبه", + "friday": "جمعه", + "saturday": "شنبه", + "sunday": "یکشنبه", + "status": "وضعیت", + "swimlane": "مسیر", + "owner": "صاحب", + "last-modified-at": "آخرین ویرایش در ", + "last-activity": "آخرین فعالیت", + "voting": "رأی دهی", + "archived": "بایگانی شده", + "delete-linked-card-before-this-card": "پیش از این‌که کارت‌های لینک شده به این کارت را حذف نکنید، نمی‌توانید این کارت را حذف کنید.", + "delete-linked-cards-before-this-list": "پیش از حذف کارت‌هایی که به کارت‌های این لیست لینک شده‌اند نمی‌توانید این لیست را حذف کنید.", + "hide-checked-items": "مخفی کردن انتخاب‌شده‌ها", + "task": "کار", + "create-task": "ایجاد کار", + "ok": "تأیید", + "organizations": "سازمان ها", + "teams": "تیم ها", + "displayName": "نام نمایشی", + "shortName": "نام کوتاه", + "website": "وبسایت", + "person": "فرد", + "my-cards": "کارت های من", + "card": "کارت", + "board": "برد", + "context-separator": "/", + "myCardsSortChange-title": "نظم کارت های من", + "myCardsSortChangePopup-title": "نظم کارت های من", + "myCardsSortChange-choice-board": "بر اساس برد", + "myCardsSortChange-choice-dueat": "بر اساس تاریخ انجام", + "dueCards-title": "کارتهای سررسید", + "dueCardsViewChange-title": "نمایش کارتهای سررسید", + "dueCardsViewChangePopup-title": "نمایش کارتهای سررسید", + "dueCardsViewChange-choice-me": "من", + "dueCardsViewChange-choice-all": "تمام کاربران", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "کارت های شکسته", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "هیچ کارتی یافت نشد", + "one-card-found": "یک کارت یافت شد", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "برد", + "operator-board-abbrev": "b", + "operator-swimlane": "مسیر", + "operator-swimlane-abbrev": "s", + "operator-list": "لیست", + "operator-list-abbrev": "l", + "operator-label": "لیبل", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "عضو", + "operator-member-abbrev": "m", + "operator-assignee": "محول‌شده", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "ناشی از", + "operator-created": "ایجاد شده", + "operator-modified": "ویرایش شده", + "operator-sort": "مرتب سازی", + "operator-comment": "نظر", + "operator-has": "دارد", + "operator-limit": "حد", + "predicate-archived": "بایگانی شده", + "predicate-open": "باز", + "predicate-ended": "پایان یافته", + "predicate-all": "همه", + "predicate-overdue": "منقضی شده", + "predicate-week": "هفته", + "predicate-month": "ماه", + "predicate-quarter": "فصل", + "predicate-year": "سال", + "predicate-due": "ناشی از", + "predicate-modified": "ویرایش شده", + "predicate-created": "ایجاد شده", + "predicate-attachment": "ضمیمه", + "predicate-description": "توضیحات", + "predicate-checklist": "چک‌لیست", + "predicate-start": "شروع", + "predicate-end": "پایان", + "predicate-assignee": "محول‌شده", + "predicate-member": "عضو", + "predicate-public": "عمومی", + "predicate-private": "خصوصی", + "operator-unknown-error": "%s اپراتور نیست", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "صفحه بعد", + "previous-page": "صفحه قبلی", + "heading-notes": "یادداشت ها", + "globalSearch-instructions-heading": "دستورالعمل جستجو", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "اپراتورها حاضر", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "جستجوی متنی به کارکتر بزرگ و کوچک حساس است", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "مرتبط به این جستجو", + "excel-font": "اِریال", + "number": "عدد", + "label-colors": "رنگ لیبل", + "label-names": "اسامی لیبل", + "archived-at": "بایگانی شده در", + "sort-cards": "مرتب‌سازی کارت‌ها", + "cardsSortPopup-title": "مرتب‌سازی کارت‌ها", + "due-date": "تاریخ اجرا", + "server-error": "خطای سرور", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "تیتر (به صورت الفبا)", + "created-at-newest-first": "ساخته شده در تاریخ (با اولویت جدید تر)", + "created-at-oldest-first": "ساخته شده در تاریخ (با اولویت قدیمی تر)", + "links-heading": "پیوندها", + "hide-system-messages-of-all-users": "مخفی‌سازی پیام‌های تمام کاربران", + "now-system-messages-of-all-users-are-hidden": "اکنون پیام‌های همه کاربران مخفی است", + "move-swimlane": "انتقال مسیر", + "moveSwimlanePopup-title": "انتقال مسیر", + "custom-field-stringtemplate": "قالب رشته", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "برای افزودن موارد بیشتر ، Enter را فشار دهید", + "creator": "سازنده", + "filesReportTitle": "گزارش پرونده‌ها", + "orphanedFilesReportTitle": "گزارش پرونده‌های یتیم", + "reports": "گزارش‌ها", + "rulesReportTitle": "گزارش قوانین", + "copy-swimlane": "کپی از مسیر", + "copySwimlanePopup-title": "کپی از مسیر", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/fi.i18n.json b/i18n/fi.i18n.json index 88e5dc7fa..19db3b57d 100644 --- a/i18n/fi.i18n.json +++ b/i18n/fi.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "ei valmistunut tarkistuslista %s", "activity-editComment": "muokkasi kommenttia %s", "activity-deleteComment": "poisti kommentin %s", + "activity-receivedDate": "muokkasi vastaanotettu päiväksi %s / %s", + "activity-startDate": "muokkasi aloituspäiväksi %s / %s", + "activity-dueDate": "muokkasi eräpäiväksi %s / %s", + "activity-endDate": "muokkasi loppumispäiväksi %s / %s", "add-attachment": "Lisää liite", "add-board": "Lisää taulu", + "add-template": "Lisää malli", "add-card": "Lisää kortti", + "add-card-to-top-of-list": "Lisää kortti listan alkuun", + "add-card-to-bottom-of-list": "Lisää kortti listan loppuun", "add-swimlane": "Lisää Swimlane", "add-subtask": "Lisää alitehtävä", "add-checklist": "Lisää tarkistuslista", @@ -113,6 +120,8 @@ "archives": "Arkisto", "template": "Malli", "templates": "Mallit", + "template-container": "Mallikontti", + "add-template-container": "Lisää mallikontti", "assign-member": "Valitse jäsen", "attached": "liitetty", "attachment": "Liitetiedosto", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Poista liitetiedosto?", "attachments": "Liitetiedostot", "auto-watch": "Automaattisesti seuraa tauluja kun ne on luotu", - "avatar-too-big": "Profiilikuva on liian suuri (enintään 70 kt)", + "avatar-too-big": "Profiilikuva on liian suuri (enintään 520 kt)", "back": "Takaisin", "board-change-color": "Muokkaa väriä", "board-nb-stars": "%s tähteä", "board-not-found": "Taulua ei löytynyt", "board-private-info": "Tämä taulu tulee olemaan <strong>yksityinen</strong>.", "board-public-info": "Tämä taulu tulee olemaan <strong>julkinen</strong>.", + "board-drag-drop-reorder-or-click-open": "Järjestele taulu ikoneita vetämällä ja pudottamalla. Klikkaa taulu ikonia avataksesi taulun.", "boardChangeColorPopup-title": "Muokkaa taulun taustaa", "boardChangeTitlePopup-title": "Nimeä taulu uudelleen", "boardChangeVisibilityPopup-title": "Muokkaa näkyvyyttä", @@ -138,6 +148,7 @@ "board-view-cal": "Kalenteri", "board-view-swimlanes": "Swimlanet", "board-view-collapse": "Pienennä", + "board-view-gantt": "Gantt", "board-view-lists": "Listat", "bucket-example": "Kuten “Laatikko lista” esimerkiksi", "cancel": "Peruuta", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Liitä mistä", "cardCustomField-datePopup-title": "Muokkaa päivää", "cardCustomFieldsPopup-title": "Muokkaa mukautettuja kenttiä", + "cardStartVotingPopup-title": "Äänestä", + "positiveVoteMembersPopup-title": "Kannattajat", + "negativeVoteMembersPopup-title": "Vastustajat", + "card-edit-voting": "Muokkaa äänestystä", + "editVoteEndDatePopup-title": "Muokkaa äänestyksen loppumispäivää", + "allowNonBoardMembers": "Salli kaikki kirjautuneet käyttäjät", + "vote-question": "Äänestys kysymys", + "vote-public": "Näytä kuka äänesti mitäkin", + "vote-for-it": "puolesta", + "vote-against": "vastaan", + "deleteVotePopup-title": "Poista ääni?", + "vote-delete-pop": "Poistaminen on lopullista. Menetät kaikki tähän äänestykseen liitetyt toimet.", + "cardStartPlanningPokerPopup-title": "Aloita suunnittelupokeri", + "card-edit-planning-poker": "Muokkaa suunnittelupokeria", + "editPokerEndDatePopup-title": "Muuta suunnittelupokerin loppumispäivää", + "poker-question": "Suunnittelupokeri", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Lopeta", + "poker-result-votes": "Äänet", + "poker-result-who": "Kuka", + "poker-replay": "Toista", + "set-estimation": "Aseta arvio", + "deletePokerPopup-title": "Poista suunnittelupokeri?", + "poker-delete-pop": "Poistaminen on lopullista. Menetät kaikki tähän suunnittelupokeriin liitetyt toimet.", "cardDeletePopup-title": "Poista kortti?", "cardDetailsActionsPopup-title": "Korttitoimet", "cardLabelsPopup-title": "Nimilaput", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Luo malli", "cards": "Kortit", "cards-count": "korttia", + "cards-count-one": "Kortti", "casSignIn": "CAS-kirjautuminen", "cardType-card": "Kortti", "cardType-linkedCard": "Linkitetty kortti", @@ -191,6 +236,7 @@ "close": "Sulje", "close-board": "Sulje taulu", "close-board-pop": "Voit palauttaa taulun klikkaamalla “Arkisto”-painiketta taululistan yläpalkista.", + "close-card": "Sulje kortti", "color-black": "musta", "color-blue": "sininen", "color-crimson": "karmiininpunainen", @@ -244,6 +290,8 @@ "current": "nykyinen", "custom-field-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän mukautetun kentän kaikista korteista ja poistaa sen historian.", "custom-field-checkbox": "Valintaruutu", + "custom-field-currency": "Valuutta", + "custom-field-currency-option": "Valuutta koodi", "custom-field-date": "Päivämäärä", "custom-field-dropdown": "Pudotusvalikko", "custom-field-dropdown-none": "(ei mitään)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Tehdäksesi tämän sinun täytyy olla tämän taulun jäsen", "error-json-malformed": "Tekstisi ei ole kelvollisessa JSON-muodossa", "error-json-schema": "JSON-tietosi ei sisällä oikeaa tietoa oikeassa muodossa", + "error-csv-schema": "CSV tietosi (pilkulla erotetut arvot)/TSV (tabilla erotetut arvot) ei sisällä oikeaa tietoa oikeassa muodossa", "error-list-doesNotExist": "Tätä listaa ei ole olemassa", "error-user-doesNotExist": "Tätä käyttäjää ei ole olemassa", "error-user-notAllowSelf": "Et voi kutsua itseäsi", "error-user-notCreated": "Tätä käyttäjää ei ole luotu", "error-username-taken": "Tämä käyttäjätunnus on jo käytössä", + "error-orgname-taken": "Tämä organisaation nimi on jo käytössä", + "error-teamname-taken": "Tämä tiimin nimi on jo käytössä", "error-email-taken": "Sähköpostiosoite on jo käytössä", "export-board": "Vie taulu", + "export-board-json": "Vie taulu JSON", + "export-board-csv": "Vie taulu CSV", + "export-board-tsv": "Vie taulu TSV", + "export-board-excel": "Vie taulu Excel", + "user-can-not-export-excel": "Käyttäjä ei voi viedä Excel tiedostoon", + "export-board-html": "Vie taulu HTML", + "export-card": "Vie kortti", + "export-card-pdf": "Vie kortti PDF", + "user-can-not-export-card-to-pdf": "Käyttäjä ei voi viedä korttia PDF tiedostoon", + "exportBoardPopup-title": "Vie taulu", + "exportCardPopup-title": "Vie kortti", "sort": "Lajittele", "sort-desc": "Klikkaa lajitellaksesi listan", "list-sort-by": "Lajittele lista:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Suodata", "filter-cards": "Suodata kortit tai listat", + "filter-dates-label": "Suodata päivämäärä", + "filter-no-due-date": "Ei eräpäivää", + "filter-overdue": "Myöhässä", + "filter-due-today": "Erääntyy tänään", + "filter-due-this-week": "Erääntyy tällä viikolla", + "filter-due-tomorrow": "Erääntyy huomenna", "list-filter-label": "Suodata listat otsikon mukaan", "filter-clear": "Poista suodatin", + "filter-labels-label": "Suodata nimilappu", "filter-no-label": "Ei nimilappua", + "filter-member-label": "Suodata jäsen", "filter-no-member": "Ei jäseniä", + "filter-assignee-label": "Suodata käsittelijä", + "filter-no-assignee": "Ei käsittelijää", + "filter-custom-fields-label": "Suodata mukautettu kenttä", "filter-no-custom-fields": "Ei mukautettuja kenttiä", "filter-show-archive": "Näytä arkistoidut listat", "filter-hide-empty": "Näytä tyhjät listat", "filter-on": "Suodatus on päällä", "filter-on-desc": "Suodatat kortteja tällä taululla. Klikkaa tästä muokataksesi suodatinta.", "filter-to-selection": "Suodata valintaan", + "other-filters-label": "Muut suodattimet", "advanced-filter-label": "Edistynyt suodatin", "advanced-filter-description": "Edistynyt suodatin mahdollistaa merkkijonon, joka sisältää seuraavat operaattorit: == != <= >= && || ( ) Operaattorien välissä käytetään välilyöntiä. Voit suodattaa kaikki mukautetut kentät kirjoittamalla niiden nimet ja arvot. Esimerkiksi: Field1 == Value1. Huom: Jos kentillä tai arvoilla on välilyöntejä, sinun on sijoitettava ne yksittäisiin lainausmerkkeihin. Esimerkki: 'Kenttä 1' == 'Arvo 1'. Voit hypätä yksittäisen kontrollimerkkien (' \\/) yli käyttämällä \\. Esimerkki: Field1 = I\\'m. Voit myös yhdistää useita ehtoja. Esimerkiksi: F1 == V1 || F1 == V2. Yleensä kaikki operaattorit tulkitaan vasemmalta oikealle. Voit muuttaa järjestystä asettamalla sulkuja. Esimerkiksi: F1 == V1 && (F2 == V2 || F2 == V3). Voit myös etsiä tekstikentistä regexillä: F1 == /Tes.*/i", "fullname": "Koko nimi", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Luo taulu", "home": "Koti", "import": "Tuo", + "impersonate-user": "Esiinny käyttäjänä", "link": "Linkitä", "import-board": "tuo taulu", "import-board-c": "Tuo taulu", "import-board-title-trello": "Tuo taulu Trellosta", "import-board-title-wekan": "Tuo taulu edellisestä viennistä", - "import-sandstorm-backup-warning": "Älä poista tietoja joita toit alkuperäisestä viennistä tai Trellosta ennen kuin tarkistat onnistuuko sulkea ja avata tämä jyvä uudelleen, vai näkyykö Board not found -virhe, joka tarkoittaa tietojen häviämistä.", - "import-sandstorm-warning": "Tuotu taulu poistaa kaikki olemassa olevan taulun tiedot ja korvaa ne tuodulla taululla.", + "import-board-title-csv": "Tuo taulu CSV/TSV", "from-trello": "Trellosta", "from-wekan": "Edellisestä viennistä", + "from-csv": "CSV/TSV muodosta", "import-board-instruction-trello": "Mene Trello-taulullasi 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti", + "import-board-instruction-csv": "Liitä pilkulla erotellut arvot (CSV)/ tabilla erotetut arvot (TSV).", "import-board-instruction-wekan": "Taulullasi, mene 'Valikko', sitten 'Vie taulu', ja kopioi teksti ladatusta tiedostosta.", "import-board-instruction-about-errors": "Jos virheitä tulee taulua tuotaessa, joskus tuonti silti toimii, ja taulu on Kaikki taulut sivulla.", "import-json-placeholder": "Liitä kelvollinen JSON-tietosi tähän", + "import-csv-placeholder": "Liitä kelvollinen CSV/TSV tietosi tähän", "import-map-members": "Vastaavat jäsenet", "import-members-map": "Tuomallasi taululla on muutamia jäseniä. Ole hyvä ja valitse tuomiasi jäseniä vastaavat käyttäjäsi", + "import-members-map-note": "Huom: Valitsemattomille jäsenille valitaan nykyinen käyttäjä.", "import-show-user-mapping": "Tarkasta vastaavat jäsenet", "import-user-select": "Valitse olemassaoleva käyttäjä jota haluat käyttää tänä käyttäjänä", "importMapMembersAddPopup-title": "Valitse käyttäjä", @@ -375,9 +453,13 @@ "list-select-cards": "Valitse kaikki kortit tässä listassa", "set-color-list": "Aseta väri", "listActionPopup-title": "Listatoimet", + "settingsUserPopup-title": "Käyttäjäasetukset", + "settingsTeamPopup-title": "Tiimiasetukset", + "settingsOrgPopup-title": "Organisaatioasetukset", "swimlaneActionPopup-title": "Swimlane-toimet", "swimlaneAddPopup-title": "Lisää Swimlane alle", "listImportCardPopup-title": "Tuo Trello-kortti", + "listImportCardsTsvPopup-title": "Tuo Excel CSV/TSV", "listMorePopup-title": "Lisää", "link-list": "Linkki tähän listaan", "list-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä ja listan poistaminen on lopullista. Tätä ei pysty peruuttamaan.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Siirrä ylimmäiseksi", "moveSelectionPopup-title": "Siirrä valinta", "multi-selection": "Monivalinta", + "multi-selection-label": "Aseta nimilappu valinnalle", + "multi-selection-member": "Aseta jäsen valinnalle", "multi-selection-on": "Monivalinta on päällä", "muted": "Vaimennettu", "muted-info": "Et saa koskaan ilmoituksia tämän taulun muutoksista", @@ -441,8 +525,9 @@ "search": "Etsi", "rules": "Säännöt", "search-cards": "Etsi kortin/listan otsikoista, kuvauksista ja mukautetuista kentistä tällä taululla ", - "search-example": "Etsittävä teksti?", + "search-example": "Kirjoita teksti jota etsit ja paina Enter", "select-color": "Valitse väri", + "select-board": "Valitse taulu", "set-wip-limit-value": "Aseta tämän listan tehtävien enimmäismäärä", "setWipLimitPopup-title": "Aseta WIP-raja", "shortcut-assign-self": "Valitse itsesi nykyiselle kortille", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Suodata korttini", "shortcut-show-shortcuts": "Tuo esiin tämä pikavalintalista", "shortcut-toggle-filterbar": "Muokkaa suodatussivupalkin näkyvyyttä", + "shortcut-toggle-searchbar": "Muokkaa etsintäsivupalkin näkyvyyttä", "shortcut-toggle-sidebar": "Muokkaa taulusivupalkin näkyvyyttä", "show-cards-minimum-count": "Näytä korttien lukumäärä jos lista sisältää enemmän kuin", "sidebar-open": "Avaa sivupalkki", @@ -481,7 +567,15 @@ "upload": "Lähetä", "upload-avatar": "Lähetä profiilikuva", "uploaded-avatar": "Profiilikuva lähetetty", + "custom-top-left-corner-logo-image-url": "Mukautettu oikean yläkulman logo kuvan URL", + "custom-top-left-corner-logo-link-url": "Mukautettu oikean yläkulma logon linkki URL", + "custom-top-left-corner-logo-height": "Mukautettu oikean yläkulman logokuvan korkeus. Oletus: 27", + "custom-login-logo-image-url": "Mukautettu kirjautumis logo kuvan URL", + "custom-login-logo-link-url": "Mukautettu kirjautumis logon linkki URL", + "text-below-custom-login-logo": "Teksti mukautetun kirjautumis logon alla", + "automatic-linked-url-schemes": "Mukautetut URL-mallit, joiden pitäisi olla automaattisesti klikattavissa. Yksi URL-malli riviä kohden", "username": "Käyttäjätunnus", + "import-usernames": "Tuo käyttäjänimet", "view-it": "Näytä se", "warn-list-archived": "varoitus: tämä kortti on Arkistossa olevassa listassa", "watch": "Seuraa", @@ -553,7 +647,8 @@ "minutes": "minuuttia", "seconds": "sekuntia", "show-field-on-card": "Näytä tämä kenttä kortilla", - "automatically-field-on-card": "Luo kenttä automaattisesti kaikille korteille", + "automatically-field-on-card": "Lisää kenttä uusille korteille", + "always-field-on-card": "Lisää kenttä kaikille korteille", "showLabel-field-on-card": "Näytä kentän nimilappu minikortilla", "yes": "Kyllä", "no": "Ei", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Salli sähköpostiosoitteen muuttaminen", "accounts-allowUserNameChange": "Salli käyttäjätunnuksen muuttaminen", "createdAt": "Luotu", + "modifiedAt": "Muokattu", "verified": "Varmistettu", "active": "Aktiivinen", "card-received": "Vastaanotettu", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Valitse väri", "assigned-by": "Tehtävänantaja", "requested-by": "Pyytäjä", + "card-sorting-by-number": "Korttien lajittelu numeroiden mukaan", "board-delete-notice": "Poistaminen on lopullista. Menetät kaikki listat, kortit ja toimet tällä taululla.", "delete-board-confirm-popup": "Kaikki listat, kortit, nimilaput ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.", "boardDeletePopup-title": "Poista taulu?", @@ -614,13 +711,16 @@ "r-delete-rule": "Poista sääntö", "r-new-rule-name": "Uuden säännön otsikko", "r-no-rules": "Ei sääntöjä", + "r-trigger": "Liipaisin", + "r-action": "Toimi", "r-when-a-card": "Kun kortti", "r-is": "on", "r-is-moved": "on siirretty", - "r-added-to": "lisätty kohteeseen", + "r-added-to": "Lisätty kohteeseen", "r-removed-from": "Poistettu kohteesta", "r-the-board": "taulu", "r-list": "lista", + "list": "Lista", "set-filter": "Aseta suodatin", "r-moved-to": "Siirretty kohteeseen", "r-moved-from": "Siirretty kohteesta", @@ -665,6 +765,7 @@ "r-of-checklist": "tarkistuslistasta", "r-send-email": "Lähetä sähköposti", "r-to": "vastaanottajalle", + "r-of": "/", "r-subject": "aihe", "r-rule-details": "Säännön yksityiskohdat", "r-d-move-to-top-gen": "Siirrä kortti listansa alkuun", @@ -725,6 +826,8 @@ "display-authentication-method": "Näytä kirjautumistapa", "default-authentication-method": "Oletuskirjautumistapa", "duplicate-board": "Tee kaksoiskappale taulusta", + "org-number": "Organisaatioiden määrä on:", + "team-number": "Tiimien määrä on:", "people-number": "Ihmisten määrä on:", "swimlaneDeletePopup-title": "Poista Swimlane?", "swimlane-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä ja swimlanen poistaminen on lopullista. Tätä ei pysty peruuttamaan.", @@ -750,6 +853,8 @@ "act-duenow": "muistutti nykyisen eräajan (__timeValue__) kortilla __card__ olevan nyt", "act-atUserComment": "Sinut mainittiin [__board__] __list__/__card__", "delete-user-confirm-popup": "Haluatko varmasti poistaa tämän käyttäjätilin? Tätä ei voi peruuttaa.", + "delete-team-confirm-popup": "Haluatko varmasti poistaa tämän tiimin? Tätä ei voi peruuttaa.", + "delete-org-confirm-popup": "Haluatko varmasti poistaa tämän organisaation? Tätä ei voi peruuttaa.", "accounts-allowUserDelete": "Salli käyttäjien poistaa tilinsä itse", "hide-minicard-label-text": "Piilota minikortin nimilappu teksti", "show-desktop-drag-handles": "Näytä työpöydän vedon kahvat", @@ -758,12 +863,200 @@ "addmore-detail": "Lisää tarkempi kuvaus", "show-on-card": "Näytä kortilla", "new": "Uusi", + "editOrgPopup-title": "Muokkaa organisaatiota", + "newOrgPopup-title": "Uusi organisaatio", + "editTeamPopup-title": "Muokkaa tiimiä", + "newTeamPopup-title": "Uusi tiimi", "editUserPopup-title": "Muokkaa käyttäjää", "newUserPopup-title": "Uusi käyttäjä", "notifications": "Ilmoitukset", "view-all": "Näytä kaikki", "filter-by-unread": "Suodata lukemattomat", "mark-all-as-read": "Merkkaa kaikki luetuksi", + "remove-all-read": "Poista kaikki luetut", "allow-rename": "Salli uudelleennimeäminen", - "allowRenamePopup-title": "Salli uudelleennimeäminen" + "allowRenamePopup-title": "Salli uudelleennimeäminen", + "start-day-of-week": "Aseta viikon alkamispäivä", + "monday": "Maanantai", + "tuesday": "Tiistai", + "wednesday": "Keskiviikko", + "thursday": "Torstai", + "friday": "Perjantai", + "saturday": "Lauantai", + "sunday": "Sunnuntai", + "status": "Tilanne", + "swimlane": "Swimlane", + "owner": "Omistaja", + "last-modified-at": "Viimeksi muokattu", + "last-activity": "Viimeisin toiminta", + "voting": "Äänestys", + "archived": "Arkistoitu", + "delete-linked-card-before-this-card": "Et voi poistaa tätä korttia ennenkuin ensin poistat linkitetyn kortin jolla on", + "delete-linked-cards-before-this-list": "Et voi poistaa tätä listaa ennenkuin poistat linkitetyt kortit jotka osoittavat kortteihin tässä listassa", + "hide-checked-items": "Piilota ruksatut kohdat", + "task": "Tehtävä", + "create-task": "Luo tehtävä", + "ok": "OK", + "organizations": "Organisaatiot", + "teams": "Tiimit", + "displayName": "Näyttönimi", + "shortName": "Lyhyt nimi", + "website": "Verkkosivusto", + "person": "Henkilö", + "my-cards": "Korttini", + "card": "Kortti", + "board": "Taulu", + "context-separator": "/", + "myCardsSortChange-title": "Korttini järjestys", + "myCardsSortChangePopup-title": "Korttini järjestys", + "myCardsSortChange-choice-board": "Taulun mukaan", + "myCardsSortChange-choice-dueat": "Eräpäivän mukaan", + "dueCards-title": "Eräpäivä kortit", + "dueCardsViewChange-title": "Eräpäivä kortit näkymä", + "dueCardsViewChangePopup-title": "Eräpäivä kortit näkymä", + "dueCardsViewChange-choice-me": "Minä", + "dueCardsViewChange-choice-all": "Kaikki käyttäjät", + "dueCardsViewChange-choice-all-description": "Näyttää kaikki keskeneräiset kortit joilla on *eräpäivä* tauluilta joihin käyttäjällä on oikeudet.", + "broken-cards": "Rikkinäiset kortit", + "board-title-not-found": "Taulua '%s' ei löytynyt.", + "swimlane-title-not-found": "Swimlanea '%s' ei löytynyt.", + "list-title-not-found": "Listaa '%s' ei löytynyt.", + "label-not-found": "Nimilappua '%s' ei löytynyt.", + "label-color-not-found": "Nimilapun väriä %s ei löytynyt.", + "user-username-not-found": "Käyttäjänimeä '%s' ei löytynyt.", + "comment-not-found": "Ei löytynyt korttia jonka kommentti sisältää tekstin'%s'.", + "globalSearch-title": "Etsi kaikilta tauluilta", + "no-cards-found": "Kortteja ei löytynyt.", + "one-card-found": "Yksi kortti löytyi", + "n-cards-found": "%s korttia löytyi", + "n-n-of-n-cards-found": "__start__-__end__ / __total__ korttia löytyi", + "operator-board": "taulu", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "nimilappu", + "operator-label-abbrev": "#", + "operator-user": "käyttäjä", + "operator-user-abbrev": "@", + "operator-member": "jäsen", + "operator-member-abbrev": "m", + "operator-assignee": "käsittelijä", + "operator-assignee-abbrev": "a", + "operator-creator": "luoja", + "operator-status": "tilanne", + "operator-due": "erääntyy", + "operator-created": "luotu", + "operator-modified": "muokattu", + "operator-sort": "lajittele", + "operator-comment": "kommentti", + "operator-has": "sisältää", + "operator-limit": "raja", + "predicate-archived": "arkistoitu", + "predicate-open": "avoin", + "predicate-ended": "päättyi", + "predicate-all": "kaikki", + "predicate-overdue": "myöhässä", + "predicate-week": "viikko", + "predicate-month": "kuukausi", + "predicate-quarter": "kvartaali", + "predicate-year": "vuosi", + "predicate-due": "erääntyy", + "predicate-modified": "muokattu", + "predicate-created": "luotu", + "predicate-attachment": "liitetiedosto", + "predicate-description": "kuvaus", + "predicate-checklist": "tarkistuslista", + "predicate-start": "alkaa", + "predicate-end": "loppuu", + "predicate-assignee": "käsittelijä", + "predicate-member": "jäsen", + "predicate-public": "julkinen", + "predicate-private": "yksityinen", + "operator-unknown-error": "%s ei ole operaattori", + "operator-number-expected": "operaattori __operator__ odotettiin numeroa, saatiin '__value__'", + "operator-sort-invalid": "lajittelutapa '%s' on virheellinen", + "operator-status-invalid": "'%s' ei ole kelvollinen tila", + "operator-has-invalid": "%s ei ole kelvollinen olemassaolo tarkistus", + "operator-limit-invalid": "%s ei ole kelvollinen raja. Rajan pitäisi olla positiivinen kokonaisluku.", + "next-page": "Seuraava sivu", + "previous-page": "Edellinen sivu", + "heading-notes": "Huomioitavaa", + "globalSearch-instructions-heading": "Etsintäohjeet", + "globalSearch-instructions-description": "Etsinnät voi sisältää operaattoreita tarkentamaan hakua. Operaattorit määritellään kirjoittamalla operaattorin nimi ja arvo eroteltuna kaksoispisteellä. Esimerkiksi, operaattori määritelmä `lista:Blokattu` rajoittaisi etsinnät kortteihin listassa nimeltä *Blokattu*. Jos arvo sisältää välilyöntejä tai erityismerkkejä sen on oltava lainausmerkeissä (esim. `__operator_list__:\"Tarkista nämä\"`).", + "globalSearch-instructions-operators": "Saatavilla olevat operaattorit:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - kortit tauluilla jotka täsmää määritettyyn *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - kortit listoilla jotka täsmää määritettyyn *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - kortit swimlaneilla jotka täsmää määritettyyn *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - kortit joilla on kommentti joka sisältää *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - kortit joilla on nimilappu joka täsmää *<color>* tai *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<nimi|väri>` - lyhenne `__operator_label__:<color>` tai `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - kortit joilla *<username>* on *jäsen* tai *käsittelijä*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - lyhenne `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards joilla *<username>* on *jäsen*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - kortit joilla *<username>* on *käsittelijä*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - kortit joissa *<username>* on kortin luoja", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - kortit joilla on eräpäivä korkeitaan *<n>* päivän päästä tästä hetkestä. `__operator_due__:__predicate_overdue__ listaa kaikki kortit joilla eräpäivä meni jo.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - kortit jotka on luotu *<n>* päivää sitten tai vähemmän", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - kortit joita on muokattu *<n>* päivää sitten tai vähemmän", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - jossa *<status>* on yksi seuraavista:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - arkistoidut kortit", + "globalSearch-instructions-status-all": "`__predicate_all__` - kaikki arkistoidut ja arkistoimattomat kortit", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - kortit joilla on loppumispäivä", + "globalSearch-instructions-status-public": "`__predicate_public__` - kortit vain julkisilla tauluilla", + "globalSearch-instructions-status-private": "`__predicate_private__` - kortit vain yksityisillä tauluilla", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - jossa *<field>* on yksi näistä: `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` tai `__predicate_member__`. Laittamalla `-` eteen *<field>* etsii niistä joissa puuttuu se arvo siitä kentästä (esim. `sisältää:-erääntyy` etsii korteista joilla ei ole erääntymispäivää).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - jossa *<sort-name>* on yksi näistä: `__predicate_due__`, `__predicate_created__` tai `__predicate_modified__`. Laskevaa järjestä varten voit laittaa `-` lajiteltavan nimen eteen.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - jossa *<n>* on positiivinen kokonaisluku, joka ilmaisee näytettävien korttien määrän sivua kohden. ", + "globalSearch-instructions-notes-1": "On mahdollista määritellä useita operaattoreita.", + "globalSearch-instructions-notes-2": "Samankaltaiset operaattorit on *TAI* yhteen. Kortit jotka täsmää mihin tahansa ehtoon palautetaan.\n`__operator_list__:Saatavilla __operator_list__:Blokattu` palauttaisi kortit jotka sisältää minkä tahansa listan nimeltä *Blokattu* tai *Saatavilla*.", + "globalSearch-instructions-notes-3": "Eri operaattorit ovat *JA* yhteen. Vain kortit jotka täsmää kaikkiin eri operaattoreihin palautetaan. `__operator_list__:Saatavilla __operator_label__:punainen` palauttaa vain kortit listasta *Saatavilla* joilla on *punainen* nimilappu.", + "globalSearch-instructions-notes-3-2": "Päivät voidaan määrittää positiivisena tai negatiivisena kokonaislukuna tai käyttäen `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` tai `__predicate_year__` nykyiselle jaksolle.", + "globalSearch-instructions-notes-4": "Tekstihaut ovat kirjainkoosta riippumattomia.", + "globalSearch-instructions-notes-5": "Oletuksena arkistoiduista korteista ei etsitä.", + "link-to-search": "Linkki tähän hakuun", + "excel-font": "Arial", + "number": "Numero", + "label-colors": "Nimilappujen värit", + "label-names": "Nimilappujen nimet", + "archived-at": "arkistoitu", + "sort-cards": "Lajittele kortit", + "cardsSortPopup-title": "Lajittele kortit", + "due-date": "Eräpäivä", + "server-error": "Palvelin virhe", + "server-error-troubleshooting": "Ole hyvä ja lähetä palvelimen tekemät lokit.\nSnap asennuksessan, komennolla: `sudo snap logs wekan.wekan`\nDocker asennuksessa, komennolla: `sudo docker logs wekan-app`", + "title-alphabetically": "Otsikko (Aakkosjärjestyksessä)", + "created-at-newest-first": "Luotu (Uusin ensin)", + "created-at-oldest-first": "Luotu (Vanhin ensin)", + "links-heading": "Linkit", + "hide-system-messages-of-all-users": "Piilota kaikkien käyttäjien järjestelmäviestit", + "now-system-messages-of-all-users-are-hidden": "Nyt kaikkien käyttäjien järjestelmäviestit on piilotettu", + "move-swimlane": "Siirrä Swimlane", + "moveSwimlanePopup-title": "Siirrä Swimlane", + "custom-field-stringtemplate": "Merkkijono malli", + "custom-field-stringtemplate-format": "Muoto (käytä %{arvo} paikanpitäjänä) ", + "custom-field-stringtemplate-separator": "Erotin (käytä tai   välilyöntinä)", + "custom-field-stringtemplate-item-placeholder": "Paina Enter lisätäksesi lisää kohtia", + "creator": "Luoja", + "filesReportTitle": "Tiedostot raportti", + "orphanedFilesReportTitle": "Orvot tiedostot raportti", + "reports": "Raportit", + "rulesReportTitle": "Säännöt raportti", + "copy-swimlane": "Kopioi Swimlane", + "copySwimlanePopup-title": "Kopioi Swimlane", + "display-card-creator": "Näytä kortin luoja", + "wait-spinner": "Odotus pyörijä", + "Bounce": "Pomppu odotus pyörijä", + "Cube": "Kuutio odotus pyörijä", + "Cube-Grid": "Kuutio ristikko odotus pyörijä", + "Dot": "Piste odotus pyörijä", + "Double-Bounce": "Tupla pomppu odotus pyörijä", + "Rotateplane": "Pyöritä tasoa odotus pyörijä", + "Scaleout": "Skaalaus ulos odotus pyörijä", + "Wave": "Aalto odotus pyörijä", + "maximize-card": "Suurenna kortti", + "minimize-card": "Pienennä kortti", + "delete-org-warning-message": "Ei voi poistaa tätä organisaatiota, ainakin yksi käyttäjä kuuluu siihen", + "delete-team-warning-message": "Ei voi poistaa tätä tiimiä, ainakin yksi käyttäjä kuuluu siihen" } \ No newline at end of file diff --git a/i18n/fr.i18n.json b/i18n/fr.i18n.json index 82d41840e..4615fbb61 100644 --- a/i18n/fr.i18n.json +++ b/i18n/fr.i18n.json @@ -72,12 +72,19 @@ "activity-checked-item-card": "a coché %s dans la checklist %s", "activity-unchecked-item-card": "a décoché %s dans la checklist %s", "activity-checklist-completed-card": "a complété la checklist __checklist__ de la carte __card__ de la liste __list__ du couloir __swimlane__ du tableau __board__", - "activity-checklist-uncompleted-card": "a rendu incomplète la checklist %s", + "activity-checklist-uncompleted-card": "a décoché la checklist %s", "activity-editComment": "commentaire modifié %s", "activity-deleteComment": "commentaire supprimé %s", + "activity-receivedDate": "date de réception éditée de %s à %s", + "activity-startDate": "date de début éditée de %s à %s", + "activity-dueDate": "date d'échéance éditée de %s à %s", + "activity-endDate": "date de fin éditée de %s à %s", "add-attachment": "Ajouter une pièce jointe", "add-board": "Ajouter un tableau", + "add-template": "Ajouter un modèle", "add-card": "Ajouter une carte", + "add-card-to-top-of-list": "Ajouter la carte en haut de la liste", + "add-card-to-bottom-of-list": "Ajouter la carte en bas de la liste", "add-swimlane": "Ajouter un couloir", "add-subtask": "Ajouter une sous-tâche", "add-checklist": "Ajouter une checklist", @@ -113,6 +120,8 @@ "archives": "Archives", "template": "Modèle", "templates": "Modèles", + "template-container": "Conteneur de modèles", + "add-template-container": "Ajouter un conteneur de modèles", "assign-member": "Affecter un participant", "attached": "joint", "attachment": "Pièce jointe", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Supprimer la pièce jointe ?", "attachments": "Pièces jointes", "auto-watch": "Surveiller automatiquement les tableaux quand ils sont créés", - "avatar-too-big": "La taille du fichier de l'avatar est trop importante (70 ko au maximum)", + "avatar-too-big": "La taille du fichier de l'avatar est trop importante (520 ko au maximum)", "back": "Retour", "board-change-color": "Changer la couleur", "board-nb-stars": "%s étoiles", "board-not-found": "Tableau non trouvé", "board-private-info": "Ce tableau sera <strong>privé</strong>", "board-public-info": "Ce tableau sera <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Glisser-déposer les icônes de tableau pour les réordonner. Cliquer sur l'icône du tableau pour l'ouvrir.", "boardChangeColorPopup-title": "Change la couleur de fond du tableau", "boardChangeTitlePopup-title": "Renommer le tableau", "boardChangeVisibilityPopup-title": "Changer la visibilité", @@ -138,13 +148,14 @@ "board-view-cal": "Calendrier", "board-view-swimlanes": "Couloirs", "board-view-collapse": "Diminuer", + "board-view-gantt": "Gantt", "board-view-lists": "Listes", "bucket-example": "Comme « todo list » par exemple", "cancel": "Annuler", "card-archived": "Cette carte est archivée", "board-archived": "Ce tableau est archivé", "card-comments-title": "Cette carte a %s commentaires.", - "card-delete-notice": "La suppression est permanente. Vous perdrez toutes les actions associées à cette carte.", + "card-delete-notice": "La suppression est définitive. Vous perdrez toutes les actions associées à cette carte.", "card-delete-pop": "Toutes les actions vont être supprimées du suivi d'activités et vous ne pourrez plus utiliser cette carte. Cette action est irréversible.", "card-delete-suggest-archive": "Vous pouvez déplacer une carte vers les archives afin de l'enlever du tableau tout en préservant l'activité.", "card-due": "À échéance", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Ajouter depuis", "cardCustomField-datePopup-title": "Modifier la date", "cardCustomFieldsPopup-title": "Éditer les champs personnalisés", + "cardStartVotingPopup-title": "Commencer un vote", + "positiveVoteMembersPopup-title": "Pour", + "negativeVoteMembersPopup-title": "Contre", + "card-edit-voting": "Éditer le vote", + "editVoteEndDatePopup-title": "Modifier la date de fin de vote", + "allowNonBoardMembers": "Autoriser tous les utilisateurs authentifiés", + "vote-question": "Question du vote", + "vote-public": "Montrer qui a voté quoi", + "vote-for-it": "pour", + "vote-against": "contre", + "deleteVotePopup-title": "Supprimer le vote ?", + "vote-delete-pop": "La suppression est définitive. Vous perdrez toutes les actions associées à ce vote.", + "cardStartPlanningPokerPopup-title": "Démarrer un planning poker", + "card-edit-planning-poker": "Éditer le planning poker", + "editPokerEndDatePopup-title": "Changer la date de fin de vote du planning poker", + "poker-question": "Planning poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finir", + "poker-result-votes": "Votes", + "poker-result-who": "Qui", + "poker-replay": "Rejouer", + "set-estimation": "Estimer", + "deletePokerPopup-title": "Supprimer ce planning poker ?", + "poker-delete-pop": "La suppression est définitive. Vous perdrez toutes les actions associées à ce planning poker.", "cardDeletePopup-title": "Supprimer la carte ?", "cardDetailsActionsPopup-title": "Actions sur la carte", "cardLabelsPopup-title": "Étiquettes", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Créer un modèle", "cards": "Cartes", "cards-count": "Cartes", + "cards-count-one": "Carte", "casSignIn": "Se connecter avec CAS", "cardType-card": "Carte", "cardType-linkedCard": "Carte liée", @@ -191,6 +236,7 @@ "close": "Fermer", "close-board": "Fermer le tableau", "close-board-pop": "Vous pouvez restaurer le tableau en cliquant sur le bouton « Archives » depuis le menu en entête.", + "close-card": "Fermer la carte", "color-black": "noir", "color-blue": "bleu", "color-crimson": "rouge cramoisi", @@ -244,6 +290,8 @@ "current": "actuel", "custom-field-delete-pop": "Cette action n'est pas réversible. Elle supprimera ce champ personnalisé de toutes les cartes et détruira son historique.", "custom-field-checkbox": "Case à cocher", + "custom-field-currency": "Devise", + "custom-field-currency-option": "Code devise", "custom-field-date": "Date", "custom-field-dropdown": "Liste de choix", "custom-field-dropdown-none": "(aucun)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Vous devez être participant de ce tableau pour faire cela", "error-json-malformed": "Votre texte JSON n'est pas valide", "error-json-schema": "Vos données JSON ne contiennent pas l'information appropriée dans un format correct", + "error-csv-schema": "Votre fichier CSV (valeurs séparées par des virgules) / TSV (valeurs séparées par des tabulations) ne contient pas d'informations au bon format", "error-list-doesNotExist": "Cette liste n'existe pas", "error-user-doesNotExist": "Cet utilisateur n'existe pas", "error-user-notAllowSelf": "Vous ne pouvez pas vous inviter vous-même", "error-user-notCreated": "Cet utilisateur n'a pas encore été créé", "error-username-taken": "Ce nom d'utilisateur est déjà utilisé", + "error-orgname-taken": "Ce nom d'organisation est déjà utilisé", + "error-teamname-taken": "Ce nom d'équipe est déjà utilisé", "error-email-taken": "Cette adresse mail est déjà utilisée", "export-board": "Exporter le tableau", + "export-board-json": "Exporter le tableau en JSON", + "export-board-csv": "Exporter le tableau en CSV", + "export-board-tsv": "Exporter le tableau en TSV", + "export-board-excel": "Exporter le tableau vers Excel", + "user-can-not-export-excel": "L'utilisateur ne peut pas exporter vers Excel", + "export-board-html": "Exporter le tableau en HTML", + "export-card": "Exporter la carte", + "export-card-pdf": "Exporter la carte en PDF", + "user-can-not-export-card-to-pdf": "L'utilisateur ne peut pas exporter de carte en PDF", + "exportBoardPopup-title": "Exporter le tableau", + "exportCardPopup-title": "Exporter la carte", "sort": "Tri", "sort-desc": "Cliquez pour trier la liste", "list-sort-by": "Trier la liste par:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtrer", "filter-cards": "Filtrer les cartes ou listes", + "filter-dates-label": "Filtrer par date", + "filter-no-due-date": "Pas de date d'échéance", + "filter-overdue": "Échue", + "filter-due-today": "Arrive à échéance aujourd'hui", + "filter-due-this-week": "Arrive à échéance cette semaine", + "filter-due-tomorrow": "Arrive à échéance demain", "list-filter-label": "Filtrer la liste par titre", "filter-clear": "Supprimer les filtres", + "filter-labels-label": "Filtrer par étiquette", "filter-no-label": "Aucune étiquette", + "filter-member-label": "Filtrer par participant", "filter-no-member": "Aucun participant", + "filter-assignee-label": "Filtrer par personne assignée", + "filter-no-assignee": "Pas de personne assignée", + "filter-custom-fields-label": "Filtrer par champs personnalisés", "filter-no-custom-fields": "Pas de champs personnalisés", "filter-show-archive": "Montrer les listes archivées", "filter-hide-empty": "Cacher les listes vides", "filter-on": "Le filtre est actif", "filter-on-desc": "Vous êtes en train de filtrer les cartes sur ce tableau. Cliquez ici pour modifier les filtres.", "filter-to-selection": "Filtre vers la sélection", + "other-filters-label": "Autres filtres", "advanced-filter-label": "Filtre avancé", "advanced-filter-description": "Le filtre avancé permet d'écrire une chaîne contenant les opérateur suivants : == != <= >= && || ( ). Les opérateurs doivent être séparés par des espaces. Vous pouvez filtrer tous les champs personnalisés en saisissant leur nom et leur valeur. Par exemple : champ1 == valeur1. Remarque : si des champs ou valeurs contiennent des espaces, vous devez les mettre entre apostrophes. Par exemple : 'champ 1' = 'valeur 1'. Pour échapper un caractère de contrôle (' \\/), vous pouvez utiliser \\. Par exemple : champ1 = I\\'m. Il est également possible de combiner plusieurs conditions. Par exemple : f1 == v1 || f2 == v2. Normalement, tous les opérateurs sont interprétés de gauche à droite. Vous pouvez changer l'ordre à l'aide de parenthèses. Par exemple : f1 == v1 and (f2 == v2 || f2 == v3). Vous pouvez également chercher parmi les champs texte en utilisant des expressions régulières : f1 == /Test.*/i", "fullname": "Nom complet", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Créer un tableau", "home": "Accueil", "import": "Importer", + "impersonate-user": "Utilisateur usurpé", "link": "Lien", "import-board": "importer un tableau", "import-board-c": "Importer un tableau", "import-board-title-trello": "Importer un tableau depuis Trello", "import-board-title-wekan": "Importer un tableau depuis un export précédent", - "import-sandstorm-backup-warning": "Ne supprimez pas les données que vous importez d'un tableau exporté d'origine ou de Trello avant de vérifier que la graine peut se fermer et s'ouvrir à nouveau ou qu'une erreur \"Tableau introuvable\" survient, sinon vous perdrez vos données.", - "import-sandstorm-warning": "Le tableau importé supprimera toutes les données du tableau et les remplacera avec celles du tableau importé.", + "import-board-title-csv": "Importer un tableau depuis CSV/TSV", "from-trello": "Depuis Trello", "from-wekan": "Depuis un export précédent", + "from-csv": "Depuis CSV/TSV", "import-board-instruction-trello": "Dans votre tableau Trello, allez sur 'Menu', puis sur 'Plus', 'Imprimer et exporter', 'Exporter en JSON' et copiez le texte du résultat", + "import-board-instruction-csv": "Déposez vos données en CSV (valeurs séparées par des virgules) ou TSV (valeurs séparées par des tabulations).", "import-board-instruction-wekan": "Dans votre tableau, allez dans 'Menu', puis 'Exporter un tableau', et copier le texte du fichier téléchargé.", "import-board-instruction-about-errors": "Si une erreur survient en important le tableau, il se peut que l'import ait fonctionné, et que le tableau se trouve sur la page \"Tous les tableaux\".", "import-json-placeholder": "Collez ici les données JSON valides", + "import-csv-placeholder": "Déposez ici vos données valides CSV/TSV", "import-map-members": "Assigner des participants", "import-members-map": "Le tableau que vous venez d'importer contient des participants. Veuillez assigner les participants que vous souhaitez importer à vos utilisateurs.", + "import-members-map-note": "Note: les participants ne concordant pas seront assignés à l'utilisateur courant.", "import-show-user-mapping": "Contrôler l'assignation des participants", "import-user-select": "Sélectionnez l'utilisateur existant que vous voulez associer à ce participant", "importMapMembersAddPopup-title": "Sélectionner le participant", @@ -375,9 +453,13 @@ "list-select-cards": "Sélectionner toutes les cartes de cette liste", "set-color-list": "Définir la couleur", "listActionPopup-title": "Actions sur la liste", + "settingsUserPopup-title": "Paramètres de l'utilisateur", + "settingsTeamPopup-title": "Paramètres de l'équipe", + "settingsOrgPopup-title": "Paramètres de l'organisation", "swimlaneActionPopup-title": "Actions du couloir", "swimlaneAddPopup-title": "Ajouter un couloir en dessous", "listImportCardPopup-title": "Importer une carte Trello", + "listImportCardsTsvPopup-title": "Importer un fichier Excel CSV/TSV", "listMorePopup-title": "Plus", "link-list": "Lien vers cette liste", "list-delete-pop": "Toutes les actions seront supprimées du fil d'activité et il ne sera plus possible de les récupérer. Cette action est irréversible.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Déplacer tout en haut", "moveSelectionPopup-title": "Déplacer la sélection", "multi-selection": "Sélection multiple", + "multi-selection-label": "Définir l'étiquette pour la sélection", + "multi-selection-member": "Définir le participant pour la sélection", "multi-selection-on": "Multi-Selection active", "muted": "Silencieux", "muted-info": "Vous ne serez jamais averti des modifications effectuées dans ce tableau", @@ -441,8 +525,9 @@ "search": "Chercher", "rules": "Règles", "search-cards": "Rechercher parmi les titres, descriptions et champs personnalisés des cartes/listes de ce tableau", - "search-example": "Texte à rechercher ?", + "search-example": "Écrivez le texte que vous recherchez et appuyez sur Entrée", "select-color": "Sélectionner une couleur", + "select-board": "Sélectionner le tableau", "set-wip-limit-value": "Définit une limite maximale au nombre de cartes de cette liste", "setWipLimitPopup-title": "Définir la limite WIP", "shortcut-assign-self": "Affecter cette carte à vous-même", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtrer mes cartes", "shortcut-show-shortcuts": "Afficher cette liste de raccourcis", "shortcut-toggle-filterbar": "Afficher/Masquer la barre latérale des filtres", + "shortcut-toggle-searchbar": "Afficher/Masquer la barre latérale de recherche", "shortcut-toggle-sidebar": "Afficher/Masquer la barre latérale du tableau", "show-cards-minimum-count": "Afficher le nombre de cartes si la liste en contient plus de", "sidebar-open": "Ouvrir le panneau", @@ -481,7 +567,15 @@ "upload": "Télécharger", "upload-avatar": "Télécharger un avatar", "uploaded-avatar": "Avatar téléchargé", + "custom-top-left-corner-logo-image-url": "URL de l'Image du logo personnalisé dans le coin supérieur gauche", + "custom-top-left-corner-logo-link-url": "Lien URL du logo personnalisé dans le coin supérieur gauche", + "custom-top-left-corner-logo-height": "Hauteur du logo personnalisé dans le coin supérieur gauche. Défaut : 27", + "custom-login-logo-image-url": "URL de l'image du logo de connexion personnalisé", + "custom-login-logo-link-url": "Lien URL du logo de connexion personnalisé", + "text-below-custom-login-logo": "Texte sous le logo de connexion personnalisé", + "automatic-linked-url-schemes": "Schémas d'URI personnalisés qui devraient être automatiquement cliquables. Un schéma d'URI par ligne", "username": "Nom d'utilisateur", + "import-usernames": "Importer les noms d'utilisateurs", "view-it": "Le voir", "warn-list-archived": "attention : cette carte est dans une liste archivée", "watch": "Suivre", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "secondes", "show-field-on-card": "Afficher ce champ sur la carte", - "automatically-field-on-card": "Créer automatiquement le champ sur toutes les cartes", + "automatically-field-on-card": "Ajouter le champ aux cartes nouvellement créées", + "always-field-on-card": "Ajouter le champ à toutes les cartes", "showLabel-field-on-card": "Indiquer l'étiquette du champ sur la mini-carte", "yes": "Oui", "no": "Non", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Autoriser le changement d'adresse mail", "accounts-allowUserNameChange": "Autoriser le changement d'identifiant", "createdAt": "Créé le", + "modifiedAt": "Modifié le", "verified": "Vérifié", "active": "Actif", "card-received": "Reçue", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choisissez une couleur", "assigned-by": "Assigné par", "requested-by": "Demandé par", + "card-sorting-by-number": "Tri numérique des cartes", "board-delete-notice": "La suppression est définitive. Vous perdrez toutes les listes, cartes et actions associées à ce tableau.", "delete-board-confirm-popup": "Toutes les listes, cartes, étiquettes et activités seront supprimées et vous ne pourrez pas retrouver le contenu du tableau. Il n'y a pas d'annulation possible.", "boardDeletePopup-title": "Supprimer le tableau ?", @@ -614,13 +711,16 @@ "r-delete-rule": "Supprimer la règle", "r-new-rule-name": "Titre de la nouvelle règle", "r-no-rules": "Pas de règles", + "r-trigger": "Déclencheur", + "r-action": "Action", "r-when-a-card": "Quand une carte", "r-is": "est", "r-is-moved": "est déplacée", - "r-added-to": "est ajoutée à", + "r-added-to": "Ajouté à", "r-removed-from": "Supprimé de", "r-the-board": "tableau", "r-list": "liste", + "list": "Liste", "set-filter": "Définir un filtre", "r-moved-to": "Déplacé vers", "r-moved-from": "Déplacé depuis", @@ -665,6 +765,7 @@ "r-of-checklist": "de la checklist", "r-send-email": "Envoyer un email", "r-to": "à", + "r-of": "sur", "r-subject": "sujet", "r-rule-details": "Détails de la règle", "r-d-move-to-top-gen": "Déplacer la carte en haut de sa liste", @@ -725,6 +826,8 @@ "display-authentication-method": "Afficher la méthode d'authentification", "default-authentication-method": "Méthode d'authentification par défaut", "duplicate-board": "Dupliquer le tableau", + "org-number": "Le nombre d'organisations est de :", + "team-number": "Le nombre d'équipes est de :", "people-number": "Le nombre d'utilisateurs est de :", "swimlaneDeletePopup-title": "Supprimer le couloir ?", "swimlane-delete-pop": "Toutes les actions vont être supprimées du suivi d'activités et vous ne pourrez plus utiliser ce couloir. Cette action est irréversible.", @@ -750,20 +853,210 @@ "act-duenow": "rappelle que l'échéance (__timeValue__) de __card__ est maintenant", "act-atUserComment": "Vous avez été mentionné dans [__board__] __list__/__card__", "delete-user-confirm-popup": "Êtes-vous sûr de vouloir supprimer ce compte ? Cette opération ne peut pas être annulée. ", + "delete-team-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette équipe ? Cette opération ne peut pas être annulée. ", + "delete-org-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette organisation ? Cette opération ne peut pas être annulée. ", "accounts-allowUserDelete": "Autoriser les utilisateurs à supprimer leur compte", - "hide-minicard-label-text": "Cacher le label de la minicarte", + "hide-minicard-label-text": "Cacher l'étiquette de la minicarte", "show-desktop-drag-handles": "Voir les poignées de déplacement du bureau", - "assignee": "Cessionnaire", - "cardAssigneesPopup-title": "Cessionnaire", + "assignee": "Personne assignée", + "cardAssigneesPopup-title": "Personne assignée", "addmore-detail": "Ajouter une description plus détaillée", "show-on-card": "Afficher sur la carte", "new": "Nouveau", + "editOrgPopup-title": "Éditer l'Organisation", + "newOrgPopup-title": "Nouvelle Organisation", + "editTeamPopup-title": "Éditer l'Équipe", + "newTeamPopup-title": "Nouvelle Équipe", "editUserPopup-title": "Éditer l'utilisateur", "newUserPopup-title": "Nouvel utilisateur", "notifications": "Notifications", "view-all": "Voir tout", "filter-by-unread": "Filtrer par non lu", "mark-all-as-read": "Marquer comme lus", + "remove-all-read": "Supprimer les lus", "allow-rename": "Autoriser le renommage", - "allowRenamePopup-title": "Autoriser le renommage" + "allowRenamePopup-title": "Autoriser le renommage", + "start-day-of-week": "Définir le jour de début de semaine", + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday": "Samedi", + "sunday": "Dimanche", + "status": "Statut", + "swimlane": "Couloir", + "owner": "Propriétaire", + "last-modified-at": "Dernière modification le", + "last-activity": "Dernière activité", + "voting": "Vote", + "archived": "Archivé", + "delete-linked-card-before-this-card": "Vous ne pouvez pas supprimer cette carte avant d'avoir d'abord supprimé la carte liée qui a", + "delete-linked-cards-before-this-list": "Vous ne pouvez pas supprimer cette liste avant d'avoir d'abord supprimé les cartes liées qui pointent vers des cartes de cette liste", + "hide-checked-items": "Cacher les éléments cochés", + "task": "Tâche", + "create-task": "Créer une tâche", + "ok": "OK", + "organizations": "Organisations", + "teams": "Équipes", + "displayName": "Nom d'Affichage", + "shortName": "Nom Court", + "website": "Site Web", + "person": "Personne", + "my-cards": "Mes Cartes", + "card": "Carte", + "board": "Tableau", + "context-separator": "/", + "myCardsSortChange-title": "Trier Mes Cartes", + "myCardsSortChangePopup-title": "Trier Mes Cartes", + "myCardsSortChange-choice-board": "Par tableau", + "myCardsSortChange-choice-dueat": "Par date d'échéance", + "dueCards-title": "Cartes Échues", + "dueCardsViewChange-title": "Vue des Cartes Échues", + "dueCardsViewChangePopup-title": "Vue des Cartes Échues", + "dueCardsViewChange-choice-me": "Moi", + "dueCardsViewChange-choice-all": "Tous les utilisateurs", + "dueCardsViewChange-choice-all-description": "Visualise toutes les cartes incomplètes avec une date *échue* pour lesquelles l'utilisateur possède les droits", + "broken-cards": "Cartes en erreur", + "board-title-not-found": "Tableau '%s' non trouvé.", + "swimlane-title-not-found": "Couloir '%s' non trouvé.", + "list-title-not-found": "Liste '%s' non trouvée.", + "label-not-found": "Étiquette '%s' non trouvée.", + "label-color-not-found": "Étiquette de couleur '%s' non trouvée", + "user-username-not-found": "Utilisateur '%s' non trouvé.", + "comment-not-found": "Carte dont le commentaire contient '%s' non trouvée", + "globalSearch-title": "Chercher dans tous les tableaux", + "no-cards-found": "Aucune carte trouvée", + "one-card-found": "Une carte trouvée", + "n-cards-found": "%s cartes trouvées", + "n-n-of-n-cards-found": "__start__-__end__ sur __total__ cartes trouvées", + "operator-board": "tableau", + "operator-board-abbrev": "t", + "operator-swimlane": "couloir", + "operator-swimlane-abbrev": "c", + "operator-list": "liste", + "operator-list-abbrev": "l", + "operator-label": "étiquette", + "operator-label-abbrev": "#", + "operator-user": "utilisateur", + "operator-user-abbrev": "@", + "operator-member": "participant", + "operator-member-abbrev": "m", + "operator-assignee": "personne assignée", + "operator-assignee-abbrev": "a", + "operator-creator": "créateur", + "operator-status": "statut", + "operator-due": "échéance", + "operator-created": "créé", + "operator-modified": "modifié", + "operator-sort": "tri", + "operator-comment": "commentaire", + "operator-has": "a", + "operator-limit": "limite", + "predicate-archived": "archivée", + "predicate-open": "ouverte", + "predicate-ended": "finie", + "predicate-all": "toutes", + "predicate-overdue": "échue", + "predicate-week": "semaine", + "predicate-month": "mois", + "predicate-quarter": "trimestre", + "predicate-year": "année", + "predicate-due": "échéance", + "predicate-modified": "modifiée", + "predicate-created": "créée", + "predicate-attachment": "pièce jointe", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "début", + "predicate-end": "fin", + "predicate-assignee": "personne assignée", + "predicate-member": "participant", + "predicate-public": "public", + "predicate-private": "privé", + "operator-unknown-error": "'%s' n'est pas un opérateur", + "operator-number-expected": "L'opérateur __operator__ n'attend pas '__value__' mais un nombre", + "operator-sort-invalid": "'%s' n'est pas valide pour le tri", + "operator-status-invalid": "'%s' n'est pas un statut valide", + "operator-has-invalid": "%s n'est pas un test valide d'existence", + "operator-limit-invalid": "%s n'est pas une limite valide. La limite doit être un entier positif.", + "next-page": "Page suivante", + "previous-page": "Page précédente", + "heading-notes": "Remarques", + "globalSearch-instructions-heading": "Instructions de recherche", + "globalSearch-instructions-description": "Les recherches peuvent inclure des opérateurs pour affiner le résultat. Les opérateurs sont précisés en écrivant l'opérateur suivi d'une valeur séparé par un deux-point. Par exemple, une spécification comme `liste:Bloqué` limiterait le résultat aux cartes qui contiennent une liste appelée *Bloqué*. Si la valeur contient des espaces ou des caractères spéciaux, elle doit être entourée d'apostrophes (par ex. `__operator_list__:\"À valider\"`). ", + "globalSearch-instructions-operators": "Opérateurs disponibles :", + "globalSearch-instructions-operator-board": "`__operator_board__:<titre>` - cartes dont le tableau correspond à *<titre>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<titre>` - cartes dont les listes correspondent à *<titre>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<titre>` - cartes dans les couloirs correspondant au *<titre>* spécifié", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<texte>` - cartes dont le commentaire contient *<texte>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<couleur>` `__operator_label__:<nom>` - cartes qui ont une étiquette correspondant à *<couleur>* ou à *<nom>*.", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<nom|couleur>` - raccourci pour `__operator_label__:<couleur>` ou `__operator_label__:<nom>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<nom>` - cartes où l'utilisateur <nom> est *assigné* ou est un *participant*.", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__nom` - raccourci pour `__operator_user__:<nom>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<nom>` - cartes pour lesquelles l'utilisateur *<nom>* est *participant*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<nom>` - cartes *assignées* à l'utilisateur *<nom>*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<utilisateur>` - cartes dont le créateur est *<utilisateur>*", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cartes qui arrive à échéance dans moins de *<n>* jours à partir d'aujourd'hui.\n`__operator_due__:__predicate_overdue__` liste toutes les cartes ayant passé la date d'échéance.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cartes qui ont été créées il y a *<n>* jours ou moins", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cartes qui ont été modifiées il y a *<n>* jours ou moins", + "globalSearch-instructions-operator-status": "`__operator_status__:<état>` - où *<état>* est l'un des choix suivants :", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - cartes qui ont été archivées.", + "globalSearch-instructions-status-all": "`__predicate_all__` - toutes les cartes : archivées et non archivées.", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cartes ayant une date de fin.", + "globalSearch-instructions-status-public": "`__predicate_public__` - uniquement les cartes qui sont dans un tableau public.", + "globalSearch-instructions-status-private": "`__predicate_private__` - uniquement les cartes qui sont dans un tableau privé.", + "globalSearch-instructions-operator-has": "`__operator_has__:<champ>` - où *<champ>* est un parmi `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` ou `__predicate_member__`. Placer un `-` au début de *<champ>* recherche l'absence de valeur dans ce champ (par exemple. __operator_has:__predicate_due` recherche les carte sans date d'échéance).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<tri>` - où *<tri>* est une méthode parmi `__predicate_due__`, `__predicate_created__` ou `__predicate_modified__`. Pour un tri descendant, préfixez la méthode par `-`.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - où *<n>* est un entier positif exprimant le nombre de cartes à afficher par page.", + "globalSearch-instructions-notes-1": "Il est possible d'utiliser plusieurs opérateurs.", + "globalSearch-instructions-notes-2": "Les opérateurs similaires deviennent *optionnels*. Les cartes correspondant à n'importe quelle condition sont retournées.\n`__operator_list__:Disponible __operator_list__:Bloquée` retournera les cartes contenues dans la liste *Disponible* ou dans la liste *Bloquée*.", + "globalSearch-instructions-notes-3": "Les opérateurs différents sont *combinés*. Seules les cartes correspondant à tous les critères sont retournées. `__operator_list__:Disponible __operator_label__:rouge` ne retourne que les cartes dans la liste *Disponible* avec une étiquette *rouge*.", + "globalSearch-instructions-notes-3-2": "Les durées en jours peuvent être précisées soit par un entier positif ou négatif soit en utilisant `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` ou `__predicate_year__` pour la période courante.", + "globalSearch-instructions-notes-4": "Les recherches textuelles ne sont pas sensibles à la casse.", + "globalSearch-instructions-notes-5": "Par défaut, les cartes archivées ne sont pas recherchées.", + "link-to-search": "Lien vers cette recherche", + "excel-font": "Arial", + "number": "Nombre", + "label-colors": "Couleurs d'étiquette", + "label-names": "Noms d'étiquette", + "archived-at": "archivée le", + "sort-cards": "Trier les cartes", + "cardsSortPopup-title": "Trier les cartes", + "due-date": "Date d'échéance", + "server-error": "Erreur serveur", + "server-error-troubleshooting": "Merci de soumettre l'erreur générée par le serveur. Pour une installation snap, lancer `sudo snap logs wekan.wekan`. Pour une installation docker, lancer `sudo docker logs wekan-app`", + "title-alphabetically": "Titre (Alphabétiquement)", + "created-at-newest-first": "Date de création (Plus récentes en premier)", + "created-at-oldest-first": "Date de création (Plus anciennes en premier)", + "links-heading": "Liens", + "hide-system-messages-of-all-users": "Masquer les messages système de tous les utilisateurs", + "now-system-messages-of-all-users-are-hidden": "Les messages système de tous les utilisateurs seront dorénavant masqués", + "move-swimlane": "Déplacer le couloir", + "moveSwimlanePopup-title": "Déplacer le Couloir", + "custom-field-stringtemplate": "Modèle de chaîne", + "custom-field-stringtemplate-format": "Format (utiliser %{valeur} pour marquer un emplacement)", + "custom-field-stringtemplate-separator": "Séparateur (utiliser ou   pour un espace)", + "custom-field-stringtemplate-item-placeholder": "Appuyez sur Entrée pour ajouter plus d'éléments", + "creator": "Créateur", + "filesReportTitle": "Rapports sur les fichiers", + "orphanedFilesReportTitle": "Rapports sur les fichiers orphelins", + "reports": "Rapports", + "rulesReportTitle": "Rapports sur les règles", + "copy-swimlane": "Copier le couloir", + "copySwimlanePopup-title": "Copie de Couloir", + "display-card-creator": "Afficher le créateur de la carte", + "wait-spinner": "Icône d'attente", + "Bounce": "Icône d'attente rebond", + "Cube": "Icône d'attente cube", + "Cube-Grid": "Icône d'attente cube filaire", + "Dot": "Icône d'attente point", + "Double-Bounce": "Icône d'attente double rebond", + "Rotateplane": "Icône d'attente plan rotatif", + "Scaleout": "Icône d'attente mise à l'échelle", + "Wave": "Icône d'attente onde", + "maximize-card": "Maximiser la carte", + "minimize-card": "Minimiser la carte", + "delete-org-warning-message": "Impossible de supprimer cette organisation, au moins un utilisateur lui appartient", + "delete-team-warning-message": "Impossible de supprimer cette équipe, au moins un utilisateur lui appartient" } \ No newline at end of file diff --git a/i18n/gl.i18n.json b/i18n/gl.i18n.json index 3596099b2..e172d3b86 100644 --- a/i18n/gl.i18n.json +++ b/i18n/gl.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Engadir anexo", "add-board": "Engadir taboleiro", + "add-template": "Add Template", "add-card": "Engadir tarxeta", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Arquivar", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Anexo", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Eliminar anexo?", "attachments": "Anexos", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Cambiar cor", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Listas", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancelar", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Etiquetas", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Tarxetas", "cards-count": "Tarxetas", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "negro", "color-blue": "azul", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "actual", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Data", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Esta lista non existe", "error-user-doesNotExist": "Este usuario non existe", "error-user-notAllowSelf": "Non é posíbel convidarse a un mesmo", "error-user-notCreated": "Este usuario non está creado", "error-username-taken": "Este nome de usuario xa está collido", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Exportar taboleiro", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar taboleiro", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtro", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Limpar filtro", + "filter-labels-label": "Filter by label", "filter-no-label": "Non hai etiquetas", + "filter-member-label": "Filter by member", "filter-no-member": "Non hai membros", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "O filtro está activado", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nome completo", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Crear taboleiro", "home": "Inicio", "import": "Importar", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "importar taboleiro", "import-board-c": "Importar taboleiro", "import-board-title-trello": "Importar taboleiro de Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "De Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Máis", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Mover arriba de todo", "moveSelectionPopup-title": "Mover selección", "multi-selection": "Selección múltipla", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Enviar", "upload-avatar": "Enviar un avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Nome de usuario", + "import-usernames": "Import Usernames", "view-it": "Velo", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Vixiar", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index b1dac9146..f2d24758f 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "רשימת המשימות %s סומנה כבלתי מושלמת", "activity-editComment": "התגובה %s נערכה", "activity-deleteComment": "התגובה %s נמחקה", + "activity-receivedDate": "תאריך הקבלה השתנה מ־%s ל־%s", + "activity-startDate": "תאריך ההתחלה השתנה מ־%s ל־%s", + "activity-dueDate": "תאריך היעד השתנה מ־%s ל־%s", + "activity-endDate": "תאריך הסיום השתנה מ־%s ל־%s", "add-attachment": "הוספת קובץ מצורף", "add-board": "הוספת לוח", + "add-template": "הוספת תבנית", "add-card": "הוספת כרטיס", + "add-card-to-top-of-list": "הוספת כרטיס לראש הרשימה", + "add-card-to-bottom-of-list": "הוספת כרטיס לתחתית הרשימה", "add-swimlane": "הוספת מסלול", "add-subtask": "הוסף תת משימה", "add-checklist": "הוספת רשימת מטלות", @@ -113,6 +120,8 @@ "archives": "להעביר לארכיון", "template": "תבנית", "templates": "תבניות", + "template-container": "מכולה לתבנית", + "add-template-container": "הוספת מכולה לתבנית", "assign-member": "הקצאת חבר", "attached": "מצורף", "attachment": "קובץ מצורף", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "למחוק קובץ מצורף?", "attachments": "קבצים מצורפים", "auto-watch": "הוספת לוחות למעקב כשהם נוצרים", - "avatar-too-big": "תמונת המשתמש גדולה מדי (70 ק״ב לכל היותר)", + "avatar-too-big": "תמונת המשתמש גדולה מדי (520 ק״ב לכל היותר)", "back": "חזרה", "board-change-color": "שינוי צבע", "board-nb-stars": "%s כוכבים", "board-not-found": "לוח לא נמצא", "board-private-info": "לוח זה יהיה <strong>פרטי</strong>.", "board-public-info": "לוח זה יהיה <strong>ציבורי</strong>.", + "board-drag-drop-reorder-or-click-open": "יש לגרור ולסדר מחדש את סמלי הלוח. לחיצה על סמל הלוח תפתח אותו.", "boardChangeColorPopup-title": "שינוי רקע ללוח", "boardChangeTitlePopup-title": "שינוי שם הלוח", "boardChangeVisibilityPopup-title": "שינוי מצב הצגה", @@ -138,13 +148,14 @@ "board-view-cal": "לוח שנה", "board-view-swimlanes": "מסלולים", "board-view-collapse": "צמצום", + "board-view-gantt": "גאנט", "board-view-lists": "רשימות", "bucket-example": "כמו למשל „רשימת המשימות“", "cancel": "ביטול", "card-archived": "כרטיס זה שמור בארכיון.", "board-archived": "הלוח עבר לארכיון", "card-comments-title": "לכרטיס זה %s תגובות.", - "card-delete-notice": "מחיקה היא סופית. כל הפעולות המשויכות לכרטיס זה תלכנה לאיוד.", + "card-delete-notice": "מחיקה היא סופית. כל הפעולות המשויכות לכרטיס זה תלכנה לאיבוד.", "card-delete-pop": "כל הפעולות יוסרו מלוח הפעילות ולא תהיה אפשרות לפתוח מחדש את הכרטיס. אין דרך חזרה.", "card-delete-suggest-archive": "על מנת להסיר כרטיסים מהלוח מבלי לאבד את היסטוריית הפעילות שלהם, ניתן לשמור אותם בארכיון.", "card-due": "תאריך יעד", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "לצרף מ־", "cardCustomField-datePopup-title": "החלפת תאריך", "cardCustomFieldsPopup-title": "עריכת שדות בהתאמה אישית", + "cardStartVotingPopup-title": "התחלת הצבעה", + "positiveVoteMembersPopup-title": "תומכים", + "negativeVoteMembersPopup-title": "יריבים", + "card-edit-voting": "שינוי הצבעה", + "editVoteEndDatePopup-title": "החלפת מועד סיום הצבעה", + "allowNonBoardMembers": "לאפשר לכל המשתמשים הרשומים", + "vote-question": "שאלת הסקר", + "vote-public": "להציג מי הצביע למה", + "vote-for-it": "בעד", + "vote-against": "נגד", + "deleteVotePopup-title": "למחוק הצבעה?", + "vote-delete-pop": "מחיקה היא לצמיתות. כל הפעולות המשויכות להצבעה זו תלכנה לאיבוד.", + "cardStartPlanningPokerPopup-title": "התחלת פוקר תכנון", + "card-edit-planning-poker": "עריכת פוקר תכנון", + "editPokerEndDatePopup-title": "החלפת מועד סיום להצבעה לפוקר תכנון", + "poker-question": "פוקר תכנון", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "סיום", + "poker-result-votes": "הצבעות", + "poker-result-who": "מי", + "poker-replay": "ביצוע מחדש", + "set-estimation": "הגדרת הערכה", + "deletePokerPopup-title": "למחוק פוקר תכנון?", + "poker-delete-pop": "מחיקה היא לצמיתות. כל הפעולות המשויכות לפוקר התכנון הזה תאבדנה.", "cardDeletePopup-title": "למחוק כרטיס?", "cardDetailsActionsPopup-title": "פעולות על הכרטיס", "cardLabelsPopup-title": "תוויות", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "יצירת תבנית", "cards": "כרטיסים", "cards-count": "כרטיסים", + "cards-count-one": "כרטיס", "casSignIn": "כניסה עם CAS", "cardType-card": "כרטיס", "cardType-linkedCard": "כרטיס מקושר", @@ -191,6 +236,7 @@ "close": "סגירה", "close-board": "סגירת לוח", "close-board-pop": "ניתן לשחזר את הלוח בלחיצה על כפתור „ארכיונים“ מהכותרת העליונה.", + "close-card": "סגירת כרטיס", "color-black": "שחור", "color-blue": "כחול", "color-crimson": "שני", @@ -244,6 +290,8 @@ "current": "נוכחי", "custom-field-delete-pop": "אין אפשרות לבטל את הפעולה. הפעולה תסיר את השדה שהותאם אישית מכל הכרטיסים ותשמיד את ההיסטוריה שלו.", "custom-field-checkbox": "תיבת סימון", + "custom-field-currency": "מטבע", + "custom-field-currency-option": "קוד מטבע", "custom-field-date": "תאריך", "custom-field-dropdown": "רשימה נגללת", "custom-field-dropdown-none": "(ללא)", @@ -297,13 +345,27 @@ "error-board-notAMember": "עליך לקבל חברות בלוח זה כדי לעשות זאת", "error-json-malformed": "הטקסט שלך אינו JSON תקין", "error-json-schema": "נתוני ה־JSON שלך לא כוללים את המידע הנכון בתבנית הנכונה", + "error-csv-schema": "ה־CSV (ערכים מופרדים בפסיקים)/‏TSV (ערכים מופרדים בטאבים) שלך לא כולל את המידע הנכון בצורה הנכונה.", "error-list-doesNotExist": "רשימה זו לא קיימת", "error-user-doesNotExist": "משתמש זה לא קיים", "error-user-notAllowSelf": "אינך יכול להזמין את עצמך", "error-user-notCreated": "משתמש זה לא נוצר", "error-username-taken": "המשתמש כבר קיים במערכת", + "error-orgname-taken": "שם הארגון הזה כבר תפוס", + "error-teamname-taken": "שם הקבוצה הזה כבר תפוס", "error-email-taken": "כתובת הדוא״ל כבר נמצאת בשימוש", "export-board": "ייצוא לוח", + "export-board-json": "ייצוא לוח ל־JSON", + "export-board-csv": "ייצוא לוח ל־CSV", + "export-board-tsv": "ייצוא לוח ל־TSV", + "export-board-excel": "ייצוא לוח ל־Excel", + "user-can-not-export-excel": "המשתמש לא יכול לייצא ל־Excel", + "export-board-html": "ייצוא לוח ל־HTML", + "export-card": "ייצוא כרטיס", + "export-card-pdf": "ייצוא כרטיס ל־PDF", + "user-can-not-export-card-to-pdf": "המשתמש לא יכול לייצא כרטיס ל־PDF", + "exportBoardPopup-title": "ייצוא לוח", + "exportCardPopup-title": "ייצוא כרטיס", "sort": "מיון", "sort-desc": "לחיצה למיון הרשימה", "list-sort-by": "מיון הרשימה לפי:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(י)", "filter": "מסנן", "filter-cards": "סינון כרטיסים או רשימות", + "filter-dates-label": "סינון לפי תאריך", + "filter-no-due-date": "אין מועד סיום", + "filter-overdue": "מועד הסיום עבר", + "filter-due-today": "מועד הסיום הוא היום", + "filter-due-this-week": "מועד הסיום הוא השבוע", + "filter-due-tomorrow": "מועד הסיום הוא מחר", "list-filter-label": "סינון רשימה לפי כותרת", "filter-clear": "ניקוי המסנן", + "filter-labels-label": "סינון לפי תווית", "filter-no-label": "אין תווית", + "filter-member-label": "סינון לפי חבר", "filter-no-member": "אין חבר כזה", + "filter-assignee-label": "סינון לפי הקצאה", + "filter-no-assignee": "אין אחראי", + "filter-custom-fields-label": "סינון לפי שדות מותאמים אישית", "filter-no-custom-fields": "אין שדות מותאמים אישית", "filter-show-archive": "הצגת רשימות שהועברו לארכיון", "filter-hide-empty": "הסתרת רשימות ריקות", "filter-on": "המסנן פועל", "filter-on-desc": "מסנן כרטיסים פעיל בלוח זה. יש ללחוץ כאן לעריכת המסנן.", "filter-to-selection": "סינון לבחירה", + "other-filters-label": "מסננים אחרים", "advanced-filter-label": "מסנן מתקדם", "advanced-filter-description": "המסנן המתקדם מאפשר לך לכתוב מחרוזת שמכילה את הפעולות הבאות: == != <= >= && || ( ) רווח מכהן כמפריד בין הפעולות. ניתן לסנן את כל השדות המותאמים אישית על ידי הקלדת שמם והערך שלהם. למשל: שדה1 == ערך1. לתשומת לבך: אם שדות או ערכים מכילים רווח, יש לעטוף אותם במירכא מכל צד. למשל: 'שדה 1' == 'ערך 1'. ניתן גם לשלב מגוון תנאים. למשל: F1 == V1 || F1 == V2. על פי רוב כל הפעולות מפוענחות משמאל לימין. ניתן לשנות את הסדר על ידי הצבת סוגריים. למשל: ( F1 == V1 && ( F2 == V2 || F2 == V3. כמו כן, ניתן לחפש בשדה טקסט באופן הבא: F1 == /Tes.*/i", "fullname": "שם מלא", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "יצירת לוח", "home": "בית", "import": "יבוא", + "impersonate-user": "התחזות למשתמש", "link": "קישור", "import-board": "ייבוא לוח", "import-board-c": "יבוא לוח", "import-board-title-trello": "ייבוא לוח מטרלו", "import-board-title-wekan": "ייבוא לוח מייצוא קודם", - "import-sandstorm-backup-warning": "עדיף לא למחוק נתונים שייובאו מייצוא מקורי או מ־Trello בטרם בדיקה האם הגרעין הזה נסגר ונפתח שוב או אם מתקבלת שגיאה על כך שהלוח לא נמצא, משמעות הדבר היא אבדן מידע.", - "import-sandstorm-warning": "הלוח שייובא ימחק את כל הנתונים הקיימים בלוח ויחליף אותם בלוח שייובא.", + "import-board-title-csv": "ייבוא לוח מ־CSV/TSV", "from-trello": "מ־Trello", "from-wekan": "מייצוא קודם", + "from-csv": "מ־CSV/TSV", "import-board-instruction-trello": "בלוח הטרלו שלך, עליך ללחוץ על ‚תפריט‘, ואז על ‚עוד‘, ‚הדפסה וייצוא‘, ‚יצוא JSON‘ ולהעתיק את הטקסט שנוצר.", + "import-board-instruction-csv": "נא להדביק את הערכים מופרדים בפסיקים (CSV)/ערכים מופרדים בטאבים (TSV) שלך.", "import-board-instruction-wekan": "בלוח שלך עליך לגשת אל ‚תפריט’, לאחר מכן ‚ייצוא לוח’ ואז להעתיק את הטקסט מהקובץ שהתקבל.", "import-board-instruction-about-errors": "גם אם התקבלו שגיאות בעת יבוא לוח, ייתכן שהייבוא עבד. כדי לבדוק זאת, יש להיכנס ל„כל הלוחות”.", "import-json-placeholder": "יש להדביק את נתוני ה־JSON התקינים לכאן", + "import-csv-placeholder": "נא להדביק את נתוני ה־CSV/TSV התקינים שלך כאן", "import-map-members": "מיפוי חברים", "import-members-map": "הלוחות המיובאים שלך מכילים חברים. נא למפות את החברים שברצונך לייבא למשתמשים שלך", + "import-members-map-note": "לתשומת לבך: חברים שאינם ממופים יוקצו למשתמש הנוכחי.", "import-show-user-mapping": "סקירת מיפוי חברים", "import-user-select": "נא לבחור את המשתמש ב־Wekan אותו ברצונך למפות אל חבר זה", "importMapMembersAddPopup-title": "בחירת משתמש", @@ -375,9 +453,13 @@ "list-select-cards": "בחירת כל הכרטיסים שברשימה זו", "set-color-list": "הגדרת צבע", "listActionPopup-title": "פעולות רשימה", + "settingsUserPopup-title": "הגדרות משתמש", + "settingsTeamPopup-title": "הגדרות צוות", + "settingsOrgPopup-title": "הגדרות ארגון", "swimlaneActionPopup-title": "פעולות על מסלול", "swimlaneAddPopup-title": "הוספת מסלול מתחת", "listImportCardPopup-title": "יבוא כרטיס מ־Trello", + "listImportCardsTsvPopup-title": "ייבוא CSV/TSV מסוג Excel", "listMorePopup-title": "עוד", "link-list": "קישור לרשימה זו", "list-delete-pop": "כל הפעולות תוסרנה מרצף הפעילות ולא תהיה לך אפשרות לשחזר את הרשימה. אין ביטול.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "העברה לראש הרשימה", "moveSelectionPopup-title": "העברת בחירה", "multi-selection": "בחירה מרובה", + "multi-selection-label": "הגדרת תווית לבחירה", + "multi-selection-member": "הגדרת חבר לבחירה", "multi-selection-on": "בחירה מרובה פועלת", "muted": "מושתק", "muted-info": "מעתה לא תתקבלנה אצלך התרעות על שינויים בלוח זה", @@ -441,8 +525,9 @@ "search": "חיפוש", "rules": "כללים", "search-cards": "חיפוש מבין כותרות של כרטיסים/רשימות, תיאורים ושדות בהתאמה אישית בלוח זה", - "search-example": "טקסט לחיפוש ?", + "search-example": "נא להקליד טקסט לחיפוש וללחוץ על Enter", "select-color": "בחירת צבע", + "select-board": "בחירת לוח", "set-wip-limit-value": "הגדרת מגבלה למספר המרבי של משימות ברשימה זו", "setWipLimitPopup-title": "הגדרת מגבלת „בעבודה”", "shortcut-assign-self": "להקצות אותי לכרטיס הנוכחי", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "סינון הכרטיסים שלי", "shortcut-show-shortcuts": "העלאת רשימת קיצורים זו", "shortcut-toggle-filterbar": "הצגה או הסתרה של סרגל צד הסינון", + "shortcut-toggle-searchbar": "הצגה או הסתרה של סרגל הצד", "shortcut-toggle-sidebar": "הצגה או הסתרה של סרגל צד הלוח", "show-cards-minimum-count": "הצגת ספירת כרטיסים אם רשימה מכילה למעלה מ־", "sidebar-open": "פתיחת סרגל צד", @@ -481,7 +567,15 @@ "upload": "העלאה", "upload-avatar": "העלאת תמונת משתמש", "uploaded-avatar": "הועלתה תמונה משתמש", + "custom-top-left-corner-logo-image-url": "כתובת תמונת לוגו משלך לפינה הימנית העליונה", + "custom-top-left-corner-logo-link-url": "כתובת קישור לוגו משלך לפינה הימנית העליונה", + "custom-top-left-corner-logo-height": "גובה לוגו מותאם אישית בפינה הימנית העליונה. בררת מחדל: 27", + "custom-login-logo-image-url": "כתובת תמונת לוגו משלך לכניסה", + "custom-login-logo-link-url": "כתובת קישור לוגו משלך לכניסה", + "text-below-custom-login-logo": "טקסט מתחת לשיטת כניסה מותאמת", + "automatic-linked-url-schemes": "סכמות כתובות בהתאמה אישית שיהפכו ללחיצות אוטומטית. סכמת כתובת אחת בשורה", "username": "שם משתמש", + "import-usernames": "ייבוא שמות משתמשים", "view-it": "הצגה", "warn-list-archived": "אזהרה: כרטיס זה הוא חלק מרשימה שנמצאת בארכיון", "watch": "לעקוב", @@ -553,7 +647,8 @@ "minutes": "דקות", "seconds": "שניות", "show-field-on-card": "הצגת שדה זה בכרטיס", - "automatically-field-on-card": "הוספת שדה לכל הכרטיסים", + "automatically-field-on-card": "הוספת שדה לכרטיסים חדשים", + "always-field-on-card": "הוספת שדה לכל הכרטיסים", "showLabel-field-on-card": "הצגת תווית של השדה בכרטיס מוקטן", "yes": "כן", "no": "לא", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "לאפשר שינוי דוא״ל", "accounts-allowUserNameChange": "לאפשר שינוי שם משתמש", "createdAt": "נוצר ב", + "modifiedAt": "נערך ב־", "verified": "עבר אימות", "active": "פעיל", "card-received": "התקבל", @@ -575,6 +671,7 @@ "setListColorPopup-title": "בחירת צבע", "assigned-by": "הוקצה על ידי", "requested-by": "התבקש על ידי", + "card-sorting-by-number": "מיון כרטיסים לפי מספר", "board-delete-notice": "מחיקה היא לצמיתות. כל הרשימות, הכרטיבים והפעולות שקשורים בלוח הזה ילכו לאיבוד.", "delete-board-confirm-popup": "כל הרשימות, הכרטיסים, התווית והפעולות יימחקו ולא תהיה לך דרך לשחזר את תכני הלוח. אין אפשרות לבטל.", "boardDeletePopup-title": "למחוק את הלוח?", @@ -614,6 +711,8 @@ "r-delete-rule": "מחיקת כל", "r-new-rule-name": "שמו של הכלל החדש", "r-no-rules": "אין כללים", + "r-trigger": "הקפצה", + "r-action": "פעולה", "r-when-a-card": "כאשר כרטיס", "r-is": "הוא", "r-is-moved": "מועבר", @@ -621,6 +720,7 @@ "r-removed-from": "מוסר מ־", "r-the-board": "הלוח", "r-list": "רשימה", + "list": "רשימה", "set-filter": "הגדרת מסנן", "r-moved-to": "מועבר אל", "r-moved-from": "מועבר מ־", @@ -665,6 +765,7 @@ "r-of-checklist": "של רשימת משימות", "r-send-email": "שליחת דוא״ל", "r-to": "אל", + "r-of": "מתוך", "r-subject": "נושא", "r-rule-details": "פרטי הכלל", "r-d-move-to-top-gen": "העברת כרטיס לראש הרשימה שלו", @@ -725,6 +826,8 @@ "display-authentication-method": "הצגת שיטת אימות", "default-authentication-method": "שיטת אימות כבררת מחדל", "duplicate-board": "שכפול לוח", + "org-number": "מספר הארגונים הוא:", + "team-number": "מספר הצוותים הוא:", "people-number": "מספר האנשים הוא:", "swimlaneDeletePopup-title": "למחוק מסלול?", "swimlane-delete-pop": "כל הפעולות יוסרו מהזנת הפעילות ולא תהיה לך אפשרות לשחזר את המסלול. אי אפשר לחזור אחורה.", @@ -750,6 +853,8 @@ "act-duenow": "הזכירה שמועד היעד הנוכחי (__timeValue__) של __card__ הוא עכשיו", "act-atUserComment": "אוזכרת תחת [__board__] __list__/__card__", "delete-user-confirm-popup": "למחוק את החשבון הזה? אי אפשר לבטל.", + "delete-team-confirm-popup": "למחוק את הצוות הזה? אי אפשר לשחזר.", + "delete-org-confirm-popup": "למחוק את הארגון הזה? אי אפשר לשחזר.", "accounts-allowUserDelete": "לאפשר למשתמשים למחוק את החשבונות של עצמם", "hide-minicard-label-text": "הסתרת טקסט התווית של מיני כרטיס", "show-desktop-drag-handles": "הצגת ידיות גרירה של שולחן העבודה", @@ -758,12 +863,200 @@ "addmore-detail": "הוספת תיאור מפורט", "show-on-card": "הצגה על הכרטיס", "new": "חדש", + "editOrgPopup-title": "עריכת ארגון", + "newOrgPopup-title": "ארגון חדש", + "editTeamPopup-title": "עריכת צוות", + "newTeamPopup-title": "צוות חדש", "editUserPopup-title": "עריכת משתמש", "newUserPopup-title": "משתמש חדש", "notifications": "הודעות", "view-all": "להציג הכול", "filter-by-unread": "סימון לפי כאלו שלא נקראו", "mark-all-as-read": "לסמן הכול כאילו שנקראו", + "remove-all-read": "הסרת כל אלו שנקראו", "allow-rename": "לאפשר שינוי שם", - "allowRenamePopup-title": "לאפשר שינוי שם" + "allowRenamePopup-title": "לאפשר שינוי שם", + "start-day-of-week": "הגדרת יום תחילת השבוע", + "monday": "יום שני", + "tuesday": "יום שלישי", + "wednesday": "יום רביעי", + "thursday": "יום חמישי", + "friday": "יום שישי", + "saturday": "שבת", + "sunday": "יום ראשון", + "status": "מצב", + "swimlane": "מסלול", + "owner": "בעלות", + "last-modified-at": "שינוי אחרון ב־", + "last-activity": "פעילות אחרונה", + "voting": "הצבעה", + "archived": "בארכיון", + "delete-linked-card-before-this-card": "לא ניתן למחוק את הכרטיס הזה לפני שמוחקים את הכרטיס המקושר שיש לו", + "delete-linked-cards-before-this-list": "לא ניתן למחוק את הרשימה הזו לפני שמוחקים את הכרטיסים שמצביעים לכרטיסים ברשימה הזו", + "hide-checked-items": "הסתרת הפריטים שסומנו", + "task": "משימה", + "create-task": "צירת משימה", + "ok": "אישור", + "organizations": "ארגונים", + "teams": "צוותים", + "displayName": "שם התצוגה", + "shortName": "שם קצר", + "website": "אתר", + "person": "איש/ה", + "my-cards": "הכרטיסים שלי", + "card": "כרטיס", + "board": "לוח", + "context-separator": "/", + "myCardsSortChange-title": "סידור הכרטיסים שלי", + "myCardsSortChangePopup-title": "סידור הכרטיסים שלי", + "myCardsSortChange-choice-board": "הלוח שלי", + "myCardsSortChange-choice-dueat": "לפי מועד סיום", + "dueCards-title": "כרטיסים שתוקפם פג", + "dueCardsViewChange-title": "תצוגת כרטיסים שתוקפם פג", + "dueCardsViewChangePopup-title": "תצוגת כרטיסים שתוקפם פג", + "dueCardsViewChange-choice-me": "אני", + "dueCardsViewChange-choice-all": "כל המשתמשים", + "dueCardsViewChange-choice-all-description": "מציג את כל הכרטיסים שלא הושלמו ושיש להם *תוקף* מלוחות שלמשתמש יש הרשאה לגשת אליהם.", + "broken-cards": "כרטיסים פגומים", + "board-title-not-found": "הלוח ‚%s’ לא נמצא.", + "swimlane-title-not-found": "המסלול ‚%s’ לא נמצא.", + "list-title-not-found": "הרשימה ‚%s’ לא נמצאה.", + "label-not-found": "התווית ‚%s’ לא נמצאה.", + "label-color-not-found": "צבע התווית %s לא נמצא.", + "user-username-not-found": "שם המשתמש ‚%s’ לא נמצא.", + "comment-not-found": "כרטיס עם הערה שמכיל את הטקסט ‚%s’ לא נמצא.", + "globalSearch-title": "חיפוש בכל הלוחות", + "no-cards-found": "לא נמצאו כרטיסים", + "one-card-found": "נמצא כרטיס אחד", + "n-cards-found": "נמצאו %s כרטיסים", + "n-n-of-n-cards-found": "__start__-__end__ מתוך __total__ כרטיסים נמצאו", + "operator-board": "לוח", + "operator-board-abbrev": "ל", + "operator-swimlane": "מסלול", + "operator-swimlane-abbrev": "מ", + "operator-list": "רשימה", + "operator-list-abbrev": "ר", + "operator-label": "תווית", + "operator-label-abbrev": "#", + "operator-user": "משתמש", + "operator-user-abbrev": "@", + "operator-member": "חבר", + "operator-member-abbrev": "ח", + "operator-assignee": "אחראי", + "operator-assignee-abbrev": "א", + "operator-creator": "יוצר", + "operator-status": "מצב", + "operator-due": "תפוגה", + "operator-created": "נוצר", + "operator-modified": "נערך", + "operator-sort": "מיון", + "operator-comment": "הערה", + "operator-has": "עם", + "operator-limit": "הגבלה", + "predicate-archived": "בארכיון", + "predicate-open": "פתוחה", + "predicate-ended": "הסתיים", + "predicate-all": "הכול", + "predicate-overdue": "מועד הסיום עבר", + "predicate-week": "שבוע", + "predicate-month": "חודש", + "predicate-quarter": "רבעון", + "predicate-year": "שנה", + "predicate-due": "תפוגה", + "predicate-modified": "נערך", + "predicate-created": "נוצר", + "predicate-attachment": "קובץ_מצורף", + "predicate-description": "תיאור", + "predicate-checklist": "רשימת משימות", + "predicate-start": "התחלה", + "predicate-end": "סיום", + "predicate-assignee": "אחראי", + "predicate-member": "חבר", + "predicate-public": "ציבורי", + "predicate-private": "פרטי", + "operator-unknown-error": "%s אינו סימון פעולה", + "operator-number-expected": "סימון הפעולה __operator__ ציפה למספר, קיבל ‚__value__’", + "operator-sort-invalid": "המיון של ‚%s’ שגוי", + "operator-status-invalid": "‚%s’ אינו מצב תקף", + "operator-has-invalid": "%s היא לא בדיקת התקיימות תקינה", + "operator-limit-invalid": "%s אינה מגבלה תקפה. מגבלה צריכה להיות מספר שלם וחיובי.", + "next-page": "העמוד הבא", + "previous-page": "העמוד הקודם", + "heading-notes": "הערות", + "globalSearch-instructions-heading": "הנחיות לחיפוש", + "globalSearch-instructions-description": "חיפושים יכולים לכלול סימוני פעולה כדי לחדד את החיפוש. ניתן לציין סימוני פעולה על ידי כתיבת שם הפעולה והערך מופרדים בנקודתיים. למשל, ציון סימון פעולה של `list:חסומים` יגביל את החיפוש לכרטיסים שבתוך רשימה בשם *חסומים*. אם הערך מכיל רווחים או תווים מיוחדים יש לתחום אותו בסימני ציטוט (למשל: `__operator_list__:\"לסקירה\"`).", + "globalSearch-instructions-operators": "סימוני פעולה זמינים:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - כרטיסים בלוחות שעונים על *<title>* שצוין", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - כרטיסים ברשימות שעונים על *<title>* שצוין", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - כרטיסים במסלולים שעונים על *<title>* שצוין", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - כרטיסים עם הערה שמכילים *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - כרטיסים שיש להם תווית שתואמת את *<color>* או את *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - קיצור של `__operator_label__:<color>` או `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - כרטיסים שהוקצו אל *<username>* או שהוא *חבר* בהם", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - קיצור של `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - כרטיסים בהם *<username>* *חבר*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - כרטיסים שהוקצו אל *<username>*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - כרטיסים שנוצרו על ידי *<username>*", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - כרטיסים שהתוקף שלהם יפוג עד עוד *<n>* ימים מעכשיו. `__operator_due__:__predicate_overdue__ מציג את כל הכרטיסים שתאריך התוקף שלהם עבר.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - כרטיסים שנוצרו לפני *<n>* ימים או פחות", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - כרטיסים שנערכו לפני *<n>* ימים או פחות", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - כאשר *<status>* יכול להיות אחד מהבאים:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - כרטיסים בארכיון", + "globalSearch-instructions-status-all": "`__predicate_all__` - כל הכרטיסים שבארכיון ומחוצה לו.", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - כרטיסים עם מועד סיום", + "globalSearch-instructions-status-public": "`__predicate_public__` - כרטיסים בלוחות ציבוריים בלבד", + "globalSearch-instructions-status-private": "`__predicate_private__` - כרטיסים בלוחות פרטיים בלבד.", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - כאשר *<field>* הוא אחד מבין `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` או `__predicate_member__`. הוספת `-` לפני חיפושי *<field>* מחפש שדות שאין בהם את הערך שצוין (למשל: `has:-due` יחפש כרטיסים בלי תאריך תפוגה).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - כאשר *<sort-name>* הוא אחד מבין `__predicate_due__`, `__predicate_created__` או `__predicate_modified__`. לסדר יורד יש להציב `-` לפני שם המיון.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - כאשר *<n>* הוא מספר שלם וחיובי שמבטא את מספר הכרטיסים שיופיעו בכל עמוד.", + "globalSearch-instructions-notes-1": "אפשר לציין מגוון סימוני פעולה.", + "globalSearch-instructions-notes-2": "על פעולות דומות חל שער לוגי *או* (*OR*). כרטיסים שתואמים אי אילו מהכללים יוחזרו.\n`__operator_list__:זמינים __operator_list__:חסומים` תחזרנה כרטיסים שמופיעים ברשימות עם השמות *חסומים* או *זמינים*.", + "globalSearch-instructions-notes-3": "על פעולות שונות חל שער לוגי *וגם* (*AND*). רק כרטיסים שתואמים את שתי הפעולות השונות יוחזרו. `__operator_list__:זמינים __operator_label__:אדום` תחזיר רק כרטיסים מהרשימה *זמינים* עם תווית בצבע *אדום*.", + "globalSearch-instructions-notes-3-2": "ניתן לציין ימים כמספר שלם חיובי או שלילי או על ידי ציון `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` או `__predicate_year__` לתקופה הנוכחית.", + "globalSearch-instructions-notes-4": "חיפושי טקסט הם תלויי רישיות.", + "globalSearch-instructions-notes-5": "כבררת מחדל לא מתבצע חיפוש בכרטיסים שבארכיון.", + "link-to-search": "קישור לחיפוש הזה", + "excel-font": "Arial", + "number": "מספר", + "label-colors": "צבעי תוויות", + "label-names": "שמות תוויות", + "archived-at": "הועבר לארכיון ב־", + "sort-cards": "מיון כרטיסים", + "cardsSortPopup-title": "מיון כרטיסים", + "due-date": "מועד סיום", + "server-error": "שגיאת שרת", + "server-error-troubleshooting": "נא להגיש את השגיאה שיצר השרת.\nבהתקנת snap יש להריץ: `sudo snap logs wekan.wekan`\nבהתקנת Docker יש להריץ: `sudo docker logs wekan-app`", + "title-alphabetically": "כותרת (לפי האלפבית)", + "created-at-newest-first": "מועד יצירה (החדש ביותר בהתחלה)", + "created-at-oldest-first": "מועד יצירה (הישן ביותר בהתחלה)", + "links-heading": "קישורים", + "hide-system-messages-of-all-users": "להסתיר את הודעות המערכת של כל המשתמשים", + "now-system-messages-of-all-users-are-hidden": "כעת הודעות המערכת של כל המשתמשים מוסתרות", + "move-swimlane": "העברת מסלול", + "moveSwimlanePopup-title": "העברת מסלול", + "custom-field-stringtemplate": "תבנית מחרוזת", + "custom-field-stringtemplate-format": "תבנית (להשתמש ב־%{value} כממלא מקום)", + "custom-field-stringtemplate-separator": "מפריד (להשתמש ב־ או ב־  כרווח)", + "custom-field-stringtemplate-item-placeholder": "יש ללחוץ על enter כדי להוסיף עוד פריטים", + "creator": "יוצר", + "filesReportTitle": "דוח קבצים", + "orphanedFilesReportTitle": "דוח קבצים יתומים", + "reports": "דוחות", + "rulesReportTitle": "דוח כללים", + "copy-swimlane": "העתקת מסלול", + "copySwimlanePopup-title": "העתקת מסלול", + "display-card-creator": "להציג את יוצר הכרטיסים", + "wait-spinner": "שבשבת המתנה", + "Bounce": "שבשבת המתנה קופצת", + "Cube": "שבשבת המתנה קוביה", + "Cube-Grid": "שבשבת המתנה קוביית קשת", + "Dot": "שבשבת המתנה נקודה", + "Double-Bounce": "שבשבת המתנה קפיצה כפולה", + "Rotateplane": "שבשבת המתנה משטח נוטה", + "Scaleout": "שבשבת המתנה התרחקות", + "Wave": "שבשבת המתנה גל", + "maximize-card": "הגדלת כרטיס", + "minimize-card": "מזעור כרטיס", + "delete-org-warning-message": "לא ניתן למחוק את הארגון הזה, יש לפחות משתמש אחד ששייך אליו", + "delete-team-warning-message": "לא ניתן למחוק את הצוות הזה, יש לפחות משתמש אחד ששייך אליו" } \ No newline at end of file diff --git a/i18n/hi.i18n.json b/i18n/hi.i18n.json index 38bd5fe85..68caeaf24 100644 --- a/i18n/hi.i18n.json +++ b/i18n/hi.i18n.json @@ -17,14 +17,14 @@ "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "कार्ड पर संपादित टिप्पणी __card__: __comment__ सूची में __list__ स्विमलेन में __swimlane__ बोर्ड पर __board__", + "act-deleteComment": "कार्ड पर हटाई गई टिप्पणी __card__: __comment__ सूची में __list__ स्विमलेन में __swimlane__ बोर्ड पर __board__", "act-createBoard": "created board __board__", "act-createSwimlane": "created swimlane __swimlane__ to board __board__", "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "बोर्ड पर कस्टम फ़ील्ड __customField__ __board__", + "act-deleteCustomField": "बोर्ड पर कस्टम फ़ील्ड __customField__ हटा दिया गया __board__", + "act-setCustomField": "कस्टम फ़ील्ड __customField__ संपादित करें: __customFieldValue__ कार्ड पर __card__ सूची में __list__ स्विमलेन __swimlane__ पर बोर्ड __board__", "act-createList": "added list __list__ to board __board__", "act-addBoardMember": "added member __member__ to board __board__", "act-archivedBoard": "Board __board__ moved to Archive", @@ -43,7 +43,7 @@ "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "कार्रवाई", - "activities": "गतिविधि", + "activities": "गतिविधियां", "activity": "क्रियाएँ", "activity-added": "जोड़ा गया %s से %s", "activity-archived": "%sसंग्रह में ले जाया गया", @@ -64,7 +64,7 @@ "activity-unchecked-item": "अचिह्नित %s अंदर में चिह्नांकन-सूची %s of %s", "activity-checklist-added": "संकलित चिह्नांकन-सूची तक %s", "activity-checklist-removed": "हटा दिया एक चिह्नांकन-सूची से %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "पूरी जाँच सूची%sकी %s", "activity-checklist-uncompleted": "अपूर्ण चिह्नांकन-सूची %s of %s", "activity-checklist-item-added": "संकलित चिह्नांकन-सूची विषय तक '%s' अंदर में %s", "activity-checklist-item-removed": "हटा दिया एक चिह्नांकन-सूची विषय से '%s' अंदर में %s", @@ -73,11 +73,18 @@ "activity-unchecked-item-card": "अचिह्नित %s अंदर में चिह्नांकन-सूची %s", "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "activity-checklist-uncompleted-card": "अपूर्ण चिह्नांकन-सूची %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-editComment": "संपादित टिप्पणी", + "activity-deleteComment": "टिप्पणी हटा दी गई", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "संलग्न करें", "add-board": "बोर्ड जोड़ें", + "add-template": "Add Template", "add-card": "कार्ड जोड़ें", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "तैरन जोड़ें", "add-subtask": "उप कार्य जोड़ें", "add-checklist": "चिह्नांकन-सूची जोड़ें", @@ -113,6 +120,8 @@ "archives": "पुरालेख", "template": "खाका", "templates": "खाका", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "आवंटित सदस्य", "attached": "संलग्न", "attachment": "संलग्नक", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "मिटाएँ संलग्नक?", "attachments": "संलग्नक", "auto-watch": "स्वचालित रूप से देखो बोर्डों जब वे बनाए जाते हैं", - "avatar-too-big": "अवतार बहुत बड़ा है (70KB अधिकतम)", + "avatar-too-big": "अवतार बहुत बड़ा है (अधिकतम 520KB)", "back": "वापस", "board-change-color": "रंग बदलना", "board-nb-stars": "%s पसंद होना", "board-not-found": "बोर्ड नहीं मिला", "board-private-info": "यह बोर्ड हो जाएगा <strong>निजी</strong>.", "board-public-info": "यह बोर्ड हो जाएगा <strong>सार्वजनिक</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "बोर्ड पृष्ठभूमि बदलें", "boardChangeTitlePopup-title": "बोर्ड का नाम बदलें", "boardChangeVisibilityPopup-title": "दृश्यता बदलें", @@ -137,7 +147,8 @@ "board-view": "बोर्ड दृष्टिकोण", "board-view-cal": "तिथि-पत्र", "board-view-swimlanes": "तैरना", - "board-view-collapse": "Collapse", + "board-view-collapse": "संक्षिप्त करें", + "board-view-gantt": "Gantt", "board-view-lists": "सूचियाँ", "bucket-example": "उदाहरण के लिए “बाल्टी सूची” की तरह", "cancel": "रद्द करें", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "से अनुलग्न करें", "cardCustomField-datePopup-title": "तारीख बदलें", "cardCustomFieldsPopup-title": "संपादित करें प्रचलन क्षेत्र", + "cardStartVotingPopup-title": "वोट शुरू करें", + "positiveVoteMembersPopup-title": "समर्थकों का", + "negativeVoteMembersPopup-title": "प्रतिद्वंद्वी", + "card-edit-voting": "मतदान संपादित करें", + "editVoteEndDatePopup-title": "मतदान समाप्ति की तारीख बदलें", + "allowNonBoardMembers": "सभी लॉग इन उपयोगकर्ताओं को अनुमति दें", + "vote-question": "मतदान का सवाल", + "vote-public": "दिखाएँ कि किसने क्या मतदान किया", + "vote-for-it": "इसके लिए", + "vote-against": "के प्रतिकूल", + "deleteVotePopup-title": "मतदान हटाएं?", + "vote-delete-pop": "हटाना स्थायी है। आप इस मतदान से जुड़े सभी कार्यों को खो देंगे।", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "मिटाएँ कार्ड?", "cardDetailsActionsPopup-title": "कार्ड क्रियाएँ", "cardLabelsPopup-title": "नामपत्र", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "खाका बनाएं", "cards": "कार्ड्स", "cards-count": "कार्ड्स", + "cards-count-one": "कार्ड", "casSignIn": "सीएएस के साथ साइन इन करें", "cardType-card": "कार्ड", "cardType-linkedCard": "जुड़े हुए कार्ड", @@ -191,6 +236,7 @@ "close": "बंद करे", "close-board": "बोर्ड बंद करे", "close-board-pop": "आप होम हेडर से \"संग्रह\" बटन पर क्लिक करके बोर्ड को पुनर्स्थापित करने में सक्षम होंगे।", + "close-card": "Close Card", "color-black": "काला", "color-blue": "नीला", "color-crimson": "गहरा लाल", @@ -223,8 +269,8 @@ "comment-only-desc": "केवल कार्ड पर टिप्पणी कर सकते हैं।", "no-comments": "कोई टिप्पणी नहीं", "no-comments-desc": "टिप्पणियां और गतिविधियां नहीं देख पा रहे हैं।", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker": "कामगार", + "worker-desc": "केवल कार्ड ले जा सकते हैं, खुद को कार्ड और टिप्पणी करने के लिए असाइन करें।", "computer": "संगणक", "confirm-subtask-delete-dialog": "क्या आप वाकई उपकार्य हटाना चाहते हैं?", "confirm-checklist-delete-dialog": "क्या आप वाकई जांचसूची हटाना चाहते हैं?", @@ -244,6 +290,8 @@ "current": "वर्तमान", "custom-field-delete-pop": "कोई पूर्ववत् नहीं है । यह सभी कार्ड से इस कस्टम क्षेत्र को हटा दें और इसके इतिहास को नष्ट कर देगा ।", "custom-field-checkbox": "निशानबक्से", + "custom-field-currency": "मुद्रा", + "custom-field-currency-option": "मुद्रा संहिता", "custom-field-date": "दिनांक", "custom-field-dropdown": "ड्रॉपडाउन सूची", "custom-field-dropdown-none": "(कोई नहीं)", @@ -297,34 +345,60 @@ "error-board-notAMember": "You need तक be एक सदस्य of यह बोर्ड तक do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "आपके JSON डेटा में सही प्रारूप में सही जानकारी शामिल नहीं है", + "error-csv-schema": "आपके CSV (कोमा सेपरेटेड वैल्यूज़) / TSV (टैब सेपरेटेड वैल्यूज़) में सही फॉर्मेट में उचित जानकारी शामिल नहीं है", "error-list-doesNotExist": "यह सूची does not exist", "error-user-doesNotExist": "यह user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "यह user is not created", "error-username-taken": "यह username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export बोर्ड", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", + "export-board-json": "JSON को निर्यात बोर्ड", + "export-board-csv": "निर्यात बोर्ड को सी.एस.वी.", + "export-board-tsv": "टी.एस.वी. को निर्यात बोर्ड", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "HTML को निर्यात बोर्ड", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export बोर्ड", + "exportCardPopup-title": "Export card", + "sort": "भांति", + "sort-desc": "क्रमबद्ध सूची पर क्लिक करें", + "list-sort-by": "सूची क्रमबद्ध करें:", + "list-label-modifiedAt": "अंतिम पहुंच समय", + "list-label-title": "सूची का नाम", + "list-label-sort": "आपका नियमावलीआदेश", + "list-label-short-modifiedAt": "(बड़ा)", + "list-label-short-title": "(साधारण)", + "list-label-short-sort": "(मध्यम)", "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", + "filter-cards": "निस्पंदन पत्ते या सूची", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "शीर्षक द्वारा निस्पंदन सूची", "filter-clear": "Clear filter", + "filter-labels-label": "लेबल द्वारा फ़िल्टर करें", "filter-no-label": "No label", + "filter-member-label": "सदस्य द्वारा फ़िल्टर करें", "filter-no-member": "No सदस्य", + "filter-assignee-label": "संपत्ति-भागी द्वारा फ़िल्टर करें", + "filter-no-assignee": "कोई अभिहस्तांकिती नहीं", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No प्रचलन क्षेत्र", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", + "filter-show-archive": "संग्रहित सूची दिखाएं", + "filter-hide-empty": "खाली सूची छिपाएं", "filter-on": "Filter is on", "filter-on-desc": "You are filtering कार्ड इस पर बोर्ड. Click here तक संपादित करें filter.", "filter-to-selection": "Filter तक selection", + "other-filters-label": "अन्य फिल्टर", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows तक write एक string containing following operators: == != <= >= && || ( ) एक space is used as एक separator between the Operators. You can filter for संपूर्ण प्रचलन क्षेत्र by typing their names और values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need तक encapsulate them के अंदर single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) तक be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally संपूर्ण operators are interpreted से left तक right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create बोर्ड", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import बोर्ड", "import-board-c": "Import बोर्ड", "import-board-title-trello": "Import बोर्ड से Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "सूचित कर बोर्ड will मिटाएँ संपूर्ण existing data on बोर्ड और replace it साथ में सूचित कर बोर्ड.", + "import-board-title-csv": "CSV / TSV से आयात बोर्ड", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "CSV / TSV से", "import-board-instruction-trello": "In your Trello बोर्ड, go तक 'Menu', then 'More', 'Print और Export', 'Export JSON', और copy the resulting text.", + "import-board-instruction-csv": "अपने कोमा सेपरेटेड वैल्यू (CSV) / ​​टैब सेपरेटेड वैल्यू (TSV) में पेस्ट करें।", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "अपना वैध CSV / TSV सामग्री यहां पेस्ट करें", "import-map-members": "Map सदस्य", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Re आलोकन सदस्य mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select संपूर्ण कार्ड अंदर में यह list", "set-color-list": "Set Color", "listActionPopup-title": "सूची Actions", + "settingsUserPopup-title": "उपयोगकर्ता सेटिंग", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "तैरन Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import एक Trello कार्ड", + "listImportCardsTsvPopup-title": "एक्सेल CSV / TSV आयात करें", "listMorePopup-title": "More", "link-list": "Link तक यह list", "list-delete-pop": "All actions हो जाएगा हटा दिया से the activity feed और you won't be able तक recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "स्थानांतरित तक Top", "moveSelectionPopup-title": "स्थानांतरित selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "आप किसी भी परिवर्तन के अधिसूचित नहीं किया जाएगा अंदर में यह बोर्ड", @@ -440,9 +524,10 @@ "save": "Save", "search": "Search", "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text तक search for?", + "search-cards": "इस बोर्ड पर कार्ड / सूची शीर्षक, विवरण और आदत क्षेत्र से खोजें", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set एक limit for the maximum number of tasks अंदर में यह list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself तक current कार्ड", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my कार्ड", "shortcut-show-shortcuts": "Bring up यह shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle बोर्ड Sidebar", "show-cards-minimum-count": "Show कार्ड count if सूची contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "आलोकन it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -524,8 +618,8 @@ "email-smtp-test-text": "You have successfully प्रेषित an email", "error-invitation-code-not-exist": "Invitation code doesn't exist", "error-notAuthorized": "You are not authorized तक आलोकन यह page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", + "webhook-title": "वेबहुक नाम", + "webhook-token": "टोकन (प्रमाणीकरण के लिए वैकल्पिक)", "outgoing-webhooks": "Outgoing Webhooks", "bidirectional-webhooks": "Two-Way Webhooks", "outgoingWebhooksPopup-title": "Outgoing Webhooks", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show यह field on कार्ड", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose संपूर्ण lists, कार्ड और actions associated साथ में यह बोर्ड.", "delete-board-confirm-popup": "All lists, कार्ड,नामपत्र , और activities हो जाएगा deleted और you won't be able तक recover the बोर्ड contents. There is no undo.", "boardDeletePopup-title": "मिटाएँ बोर्ड?", @@ -614,13 +711,16 @@ "r-delete-rule": "मिटाएँ rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "हटा दिया from", "r-the-board": "the बोर्ड", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "स्थानांतरित to", "r-moved-from": "स्थानांतरित from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "स्थानांतरित कार्ड तक top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "कार्ड", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/hr.i18n.json b/i18n/hr.i18n.json new file mode 100644 index 000000000..3caafdbe1 --- /dev/null +++ b/i18n/hr.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Prihvati", + "act-activity-notify": "Obavijest o aktivnostima", + "act-addAttachment": "dodao/la je privitak __attachment__ kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-deleteAttachment": "obrisao/la je privitak __attachment__ sa kartice __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addSubtask": "dodao/la je podzadatak __subtask__ kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addLabel": "Dodao/la je oznaku __label__ kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addedLabel": "Dodao/la je oznaku __label__ kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-removeLabel": "Obrisao/la je oznaku __label__ sa kartice __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-removedLabel": "Obrisao/la je oznaku __label__ sa kartice __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addChecklist": "dodao/la je listu provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addChecklistItem": "dodao/la je stavku __checklistItem__ na listu provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-removeChecklist": "obrisao/la je listu provjere __checklist__ sa kartice __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-removeChecklistItem": "obrisao/la je stavku __checklistItem__ sa liste provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-checkedItem": "označio/la je stavku __checklistItem__ kao gotovu na listi provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-uncheckedItem": "odznačio/la je stavku __checklistItem__ na listi provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-completeChecklist": "završio/la je listu provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-uncompleteChecklist": "nije završio/la listu provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-addComment": "komentirao/la je na kartici __card__: __comment__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-editComment": "izmjenio/la je komentar na kartici __card__: __comment__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-deleteComment": "obrisao/la je komentar na kartici __card__: __comment__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-createBoard": "kreirao/la je ploču __board__", + "act-createSwimlane": "kreirao/la je traku __swimlane__ na ploči __board__", + "act-createCard": "kreirao/la je karticu __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-createCustomField": "je kreirao/la prilagođeno polje __customField__ na ploči __board__", + "act-deleteCustomField": "obrisao/la je prilagođeno polje __customField__ na ploči __board__", + "act-setCustomField": "izmjenio/la je prilagođeno polje __customField__: __customFieldValue__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-createList": "dodao/la je listu __list__ ploči __board__", + "act-addBoardMember": "dodao/la je korisnika __member__ ploči __board__", + "act-archivedBoard": "Ploča __board__ prebačena je u Arhivu", + "act-archivedCard": "Kartica __card__ na listi __list__ u traci __swimlane__ na ploči __board__ prebačena je u Arhivu", + "act-archivedList": "Lista __list__ u traci __swimlane__ na ploči __board__ prebačena je u Arhivu", + "act-archivedSwimlane": "Traka __swimlane__ na ploči __board__ prebačena je u Arhivu", + "act-importBoard": "uvezao/la je ploču __board__", + "act-importCard": "uvezao/la je karticu __card__ na listu __list__ u traci __swimlane__ na ploči __board__", + "act-importList": "uvezao/la je listu __list__ na traku __swimlane__ na ploči __board__", + "act-joinMember": "dodao/la je korisnika __member__ kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__", + "act-moveCard": "prebacio/la je karticu __card__ na ploču __board__ sa liste __oldList__ na traci __oldSwimlane__ u listu __list__ na traci __swimlane__", + "act-moveCardToOtherBoard": "prebacio/la je karticu __card__ sa liste __oldList__ u traci __oldSwimlane__ na ploči __oldBoard__ u listu __list__ na traci __swimlane__ na ploči __board__", + "act-removeBoardMember": "uklonio/la je korisnika __member__ sa ploče __board__", + "act-restoredCard": "vratio/la je karticu __card__ na listu __list__ u traci __swimlane__ na ploči __board__", + "act-unjoinMember": "uklonio/la je korisnika __member__ sa kartice __card__ na list __list__ u traci __swimlane__ na ploči __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Radnje", + "activities": "Aktivnosti", + "activity": "Aktivnost", + "activity-added": "dodao/la je %s u %s", + "activity-archived": "%s prebačen je u Arhivu", + "activity-attached": "dodao/la je %s u %s", + "activity-created": "kreirao/la je %s", + "activity-customfield-created": "kreirao/la je prilagođeno polje %s", + "activity-excluded": "izdvojio/la je %s iz %s", + "activity-imported": "uvezeno %s u %s iz %s ", + "activity-imported-board": "uvezeno %s iz %s", + "activity-joined": "spojen", + "activity-moved": "moved %s from %s to %s", + "activity-on": "na %s", + "activity-removed": "uklonjen %s iz %s", + "activity-sent": "poslano %s u %s", + "activity-unjoined": "odspojen %s", + "activity-subtask-added": "dodan podzadatak u %s", + "activity-checked-item": "označen %s u listi provjere %s od %s", + "activity-unchecked-item": "odznačen %s u listi provjere %s od %s", + "activity-checklist-added": "dodana lista provjere u %s", + "activity-checklist-removed": "uklonjena lista provjere iz %s", + "activity-checklist-completed": "dovršena lista provjere %s od %s", + "activity-checklist-uncompleted": "nedovršena lista provjere %s od %s", + "activity-checklist-item-added": "dodana stavka '%s' u %s", + "activity-checklist-item-removed": "uklonjena stavka iz '%s' u %s", + "add": "Dodaj", + "activity-checked-item-card": "označeno %s u listi provjere %s", + "activity-unchecked-item-card": "odznačeno %s u listi provjere %s", + "activity-checklist-completed-card": "lista provjere __checklist__ na kartici __card__ na listi __list__ u traci __swimlane__ na ploči __board__ je potpuno završena", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "uređen komentar %s", + "activity-deleteComment": "obrisan komentar %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Dodaj privitak", + "add-board": "Dodaj ploču", + "add-template": "Add Template", + "add-card": "Dodaj karticu", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Dodaj traku", + "add-subtask": "Dodaj podzadatak", + "add-checklist": "Dodaj listu provjere", + "add-checklist-item": "Dodaj stavku listi provjere", + "add-cover": "Dodaj naslovnicu", + "add-label": "Dodaj oznaku", + "add-list": "Dodaj listu", + "add-members": "Dodaj korisnika", + "added": "Dodano", + "addMemberPopup-title": "Korisnici", + "admin": "Administrator", + "admin-desc": "Može pregledavati i uređivati ​​kartice, uklanjati članove i mijenjati postavke ploče.", + "admin-announcement": "Obavijest", + "admin-announcement-active": "Aktivne sistemske obavijesti", + "admin-announcement-title": "Administratorske obavijesti", + "all-boards": "Sve ploče", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Primjeni", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Preseli u arhivu", + "archive-all": "Preseli sve u arhivu", + "archive-board": "Preseli ploču u arhivu", + "archive-card": "Preseli karticu u arhivu", + "archive-list": "Preseli listu u arhivu", + "archive-swimlane": "Preseli traku u arhivu", + "archive-selection": "Preseli selekciju u arhivu", + "archiveBoardPopup-title": "Želite preseliti ploču u arhivu?", + "archived-items": "Arhiva", + "archived-boards": "Ploče u arhivi", + "restore-board": "Vrati ploču", + "no-archived-boards": "Nema ploča u arhivi.", + "archives": "Arhiva", + "template": "Predložak", + "templates": "Predlošci", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Dodijeli člana", + "attached": "u prilogu", + "attachment": "Privitak", + "attachment-delete-pop": "Brisanje priloga je trajno. Nema poništavanja brisanja.", + "attachmentDeletePopup-title": "Obrisati privitak?", + "attachments": "Privitci", + "auto-watch": "Automatski pratite nove ploče kada su kreirane", + "avatar-too-big": "Slika za avatar je prevelika (maksimalno 520KB)", + "back": "Nazad", + "board-change-color": "Promijeni boju", + "board-nb-stars": "%s zvjezdica", + "board-not-found": "Ploča nije pronađena", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Preimenuj ploču", + "boardChangeVisibilityPopup-title": "Promijeni vidljivost", + "boardChangeWatchPopup-title": "Promijeni praćenje", + "boardMenuPopup-title": "Postavke ploče", + "boardChangeViewPopup-title": "Board View", + "boards": "Ploče", + "board-view": "Board View", + "board-view-cal": "Kalendar", + "board-view-swimlanes": "Trake", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Liste", + "bucket-example": "Na primjer, poput \"Liste zadataka\"", + "cancel": "Odustani", + "card-archived": "Ova kartica prebačena je u arhivu.", + "board-archived": "Ova ploča prebačena je u arhivu.", + "card-comments-title": "Broj komentara na ovoj kartici je %s.", + "card-delete-notice": "Brisanje je trajno. Izgubit ćete sve radnje povezane s ovom karticom.", + "card-delete-pop": "Sve radnje će biti uklonjene iz liste aktivnosti i nećete moći ponovo otvoriti karticu. Nema poništavanja radnje.", + "card-delete-suggest-archive": "Karticu možete premjestiti u arhivu da biste je uklonili s ploče i sačuvali aktivnost.", + "card-due": "Dospijeće", + "card-due-on": "Dospijeće na", + "card-spent": "Provedeno vrijeme", + "card-edit-attachments": "Uredi privitke", + "card-edit-custom-fields": "Uredi prilagođena polja", + "card-edit-labels": "Uredi oznake", + "card-edit-members": "Uredi korisnike", + "card-labels-title": "Promijenite oznake na kartici.", + "card-members-title": "Dodajte ili uklonite članove ploče s kartice.", + "card-start": "Početak", + "card-start-on": "Počinje od", + "cardAttachmentsPopup-title": "Privitak od", + "cardCustomField-datePopup-title": "Promjena datuma", + "cardCustomFieldsPopup-title": "Uredi prilagođena polja", + "cardStartVotingPopup-title": "Pokreni glasanje", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Uredi glasanje", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "je za", + "vote-against": "je protiv", + "deleteVotePopup-title": "Obrisati glasovanje?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Obrisati karticu?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Oznake", + "cardMembersPopup-title": "Korisnici", + "cardMorePopup-title": "Više", + "cardTemplatePopup-title": "Dodati predložak", + "cards": "Kartice", + "cards-count": "Kartice", + "cards-count-one": "Kartica", + "casSignIn": "Prijavite se s CAS-om", + "cardType-card": "Kartica", + "cardType-linkedCard": "Povezane kartice", + "cardType-linkedBoard": "Povezane ploče", + "change": "Promijeni", + "change-avatar": "Promijeni avatara", + "change-password": "Promijeni lozinku", + "change-permissions": "Promijeni dozvole", + "change-settings": "Promijeni postavke", + "changeAvatarPopup-title": "Promijeni avatara", + "changeLanguagePopup-title": "Promijeni jezik", + "changePasswordPopup-title": "Promijeni lozinku", + "changePermissionsPopup-title": "Promijeni dozvole", + "changeSettingsPopup-title": "Promijeni postavke", + "subtasks": "Podzadaci", + "checklists": "Liste provjere", + "click-to-star": "Kliknite da biste ovu ploču označili zvjezdicom.", + "click-to-unstar": "Kliknite da biste uklonili zvjezdicu s ove ploče.", + "clipboard": "Međuspremnik ili povucite i ispustite", + "close": "Zatvori", + "close-board": "Zatvori ploču", + "close-board-pop": "Ploču ćete moći vratiti klikom na gumb \"Arhiviraj\" u zaglavlju.", + "close-card": "Close Card", + "color-black": "crna", + "color-blue": "plava", + "color-crimson": "tamnocrvena", + "color-darkgreen": "tamnozelena", + "color-gold": "zlatna", + "color-gray": "siva", + "color-green": "zelena", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "mornarsko plava", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "ružičasta", + "color-plum": "plum", + "color-purple": "ljubičasta", + "color-red": "crvena", + "color-saddlebrown": "saddlebrown", + "color-silver": "srebrna", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "bijela", + "color-yellow": "žuta", + "unset-color": "Unset", + "comment": "Komentiraj", + "comment-placeholder": "Napiši komentar", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "Nema komentara", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Pretraži", + "copyCardPopup-title": "Kopiraj karticu", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Dodaj", + "createBoardPopup-title": "Dodaj ploču", + "chooseBoardSourcePopup-title": "Uvezi ploču", + "createLabelPopup-title": "Dodaj oznaku", + "createCustomField": "Dodaj polje", + "createCustomFieldPopup-title": "Dodaj polje", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Lista provjere", + "custom-field-currency": "Valuta", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Datum", + "custom-field-dropdown": "Padajuća lista", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "Opcije popisa", + "custom-field-dropdown-options-placeholder": "Pritisnite enter da biste dodali više opcija", + "custom-field-dropdown-unknown": "(nepoznato)", + "custom-field-number": "Broj", + "custom-field-text": "Tekst", + "custom-fields": "Prilagođena polja", + "date": "Datum", + "decline": "Odustani", + "default-avatar": "Zadani avatar", + "delete": "Obriši", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Odbaci", + "done": "Done", + "download": "Preuzmi", + "edit": "Uredi", + "edit-avatar": "Promijeni avatara", + "edit-profile": "Uredi profil", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Promijeni datum početka", + "editCardDueDatePopup-title": "Promijeni datum dospijeća", + "editCustomFieldPopup-title": "Uredi polje", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Promijeni oznaku", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Uredi profil", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email je poslan", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "Bez oznake", + "filter-member-label": "Filter by member", + "filter-no-member": "Bez korisnika", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter je uključen", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Dodaj ploču", + "home": "Početna", + "import": "Uvoz", + "impersonate-user": "Impersonate user", + "link": "Poveznica", + "import-board": "import board", + "import-board-c": "Uvezi ploču", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Označi korisnika", + "info": "Inačica", + "initials": "Inicijali", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Dodaj oznaku", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Oznake", + "language": "Jezik", + "last-admin-desc": "Ne možeš promijeniti uloge jer mora postojati barem jedan administrator.", + "leave-board": "Napusti ploču", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Napustiti ploču?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Označi sve kartice na ovoj listi", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "Više", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Liste", + "swimlanes": "Trake", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Korisnici", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Bez obavijesti", + "muted-info": "Nikada nećeš biti obaviješten o bilo kakvim promjenama na ovoj ploči", + "my-boards": "Moje ploče", + "name": "Ime", + "no-archived-cards": "Nema kartica u arhivi", + "no-archived-lists": "Nema listi u arhivi", + "no-archived-swimlanes": "Nema traka u arhivi", + "no-results": "Bez rezultata", + "normal": "Normalno", + "normal-desc": "Može pregledavati i uređivati ​​kartice. Nije moguće promijeniti postavke.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "neobavezno", + "or": "ili", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Stranica nije pronađena.", + "password": "Lozinka", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Pretpregled", + "previewAttachedImagePopup-title": "Pretpregled", + "previewClipboardImagePopup-title": "Pretpregled", + "private": "Privatno", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profil", + "public": "Javno", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Ukloniti sa ploče", + "remove-label": "Ukloni oznaku", + "listDeletePopup-title": "Ukloni listu?", + "remove-member": "Ukloniti korisnika", + "remove-member-from-card": "Ukloni sa kartice", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Ukloniti korisnika?", + "rename": "Preimenovati", + "rename-board": "Preimenuj ploču", + "restore": "Restore", + "save": "Spremi", + "search": "Traži", + "rules": "Pravila", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Pretplati se", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Prekovremeno", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Vrijeme", + "title": "Naziv", + "tracking": "Tracking", + "tracking-info": "Bit ćeš obaviješten o svim promjenama na onim karticama u koje si uključen kao autor ili član.", + "type": "Tip", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Korisničko ime", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Prati", + "watching": "Watching", + "watching-info": "Bit ćeš obaviješten o svim promjenama na ovoj ploči", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Osnove", + "welcome-list2": "Napredno", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Administratorska ploča", + "settings": "Postavke", + "people": "People", + "registration": "Registracija", + "disable-self-registration": "Disable Self-Registration", + "invite": "Pozovi", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Korisničko ime", + "smtp-password": "Lozinka", + "smtp-tls": "TLS support", + "send-from": "Od", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Nepoznato)", + "Node_version": "Node inačica", + "Meteor_version": "Meteor inačica", + "MongoDB_version": "MongoDB inačica", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "dani", + "hours": "sati", + "minutes": "minute", + "seconds": "sekunde", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Da", + "no": "Ne", + "accounts": "Računi", + "accounts-allowEmailChange": "Dozvoli promjenu Email-a", + "accounts-allowUserNameChange": "Dozvoli promjenu korisničkog imena", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Zadano od", + "requested-by": "Zatraženo od", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Obrisati ploču?", + "delete-board": "Obriši ploču", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Zadano", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Dodaj pravilo", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "je", + "r-is-moved": "je pomaknuto", + "r-added-to": "Dodano je", + "r-removed-from": "Uklonjeno je sa", + "r-the-board": "ploča", + "r-list": "lista", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "kartica", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "ime liste", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "ime", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Preseli u arhivu", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Dodati", + "r-remove": "Ukloniti", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Označiti", + "r-uncheck": "Odznačiti", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "prema", + "r-of": "od", + "r-subject": "predmet", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "prema", + "r-d-send-email-subject": "predmet", + "r-d-send-email-message": "tekst", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Kartica", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Broj", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/hu.i18n.json b/i18n/hu.i18n.json index 7f450ffc8..009407dd5 100644 --- a/i18n/hu.i18n.json +++ b/i18n/hu.i18n.json @@ -1,8 +1,8 @@ { "accept": "Elfogadás", "act-activity-notify": "Tevékenység értesítés", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addAttachment": "hozzáadta __attachment__ mellékletet __card__ kártyához __list__ listán __swimlane__ elválasztón __board__ táblán", + "act-deleteAttachment": "törölte __attachment__ mellékletet __card__ kártyához __list__ listán __swimlane__ elválasztón __board__ táblán", "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -14,7 +14,7 @@ "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "befejezte __checklist__ listát ezen a kártyán: __card__ ebben az oszlopban: __list__ ezen az úszósávon: __swimlane__ at ezen a kártyán: __board__", "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -46,7 +46,7 @@ "activities": "Tevékenységek", "activity": "Tevékenység", "activity-added": "%s hozzáadva ehhez: %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "%s áthelyezve az Archívumba", "activity-attached": "%s mellékletet csatolt a kártyához: %s", "activity-created": "%s létrehozva", "activity-customfield-created": "létrehozta a(z) %s egyéni mezőt", @@ -60,25 +60,32 @@ "activity-sent": "%s elküldve ide: %s", "activity-unjoined": "%s kilépett a csoportból", "activity-subtask-added": "Alfeladat hozzáadva ehhez: %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checked-item": "Kipipálta %s ebben a Listában: %s ebből: %s", + "activity-unchecked-item": "Pipátlanította %s ebben a Listában: %s ebből:%s", "activity-checklist-added": "ellenőrzőlista hozzáadva ehhez: %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-removed": "eltávolította az ellenőrzőlistát innen: %s", + "activity-checklist-completed": "befejezte %s ellenőrzőlistát innen: %s", + "activity-checklist-uncompleted": "befejezetlenné tett %s elemet %s listából", "activity-checklist-item-added": "ellenőrzőlista elem hozzáadva ehhez: „%s”, ebben: %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "activity-checklist-item-removed": "eltávolított egy ellenőrző-lista elemet innen: '%s' ebben: %s", "add": "Hozzáadás", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-checked-item-card": "bepipálta %s elemet ebben a listában: %s", + "activity-unchecked-item-card": "pipáltanította %s elemet ebben a listában: %s", + "activity-checklist-completed-card": "befejezte ezt a listát: __checklist__ ezen a kártyán: __card__ ebben az oszlopban: __list__ ezen az úszósávon: __swimlane__ at ezen a kártyán: __board__", + "activity-checklist-uncompleted-card": "befejezetlenné tette ezt a listát: __checklist__ ezen a kártyán: __card__ ebben az oszlopban: __list__ ezen az úszósávon: __swimlane__ at ezen a kártyán: __board__", + "activity-editComment": "szerkesztette ezt a megjegyzést: %s", + "activity-deleteComment": "törölte ezt a megjegyzést: %s", + "activity-receivedDate": "átírta az \"érkezett\" dátumot erről: %s erre: %s", + "activity-startDate": "átírta az \"elkezdve\" dátumot erről: %s erre: %s", + "activity-dueDate": "átírta a *határidő* dátumát erről: %s erre: %s", + "activity-endDate": "átírta a \"befejezés\" dátumát erről: %s erre: %s", "add-attachment": "Melléklet hozzáadása", "add-board": "Tábla hozzáadása", + "add-template": "Add Template", "add-card": "Kártya hozzáadása", - "add-swimlane": "Add Swimlane", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Úszósáv hozzáadása", "add-subtask": "Alfeladat hozzáadása", "add-checklist": "Ellenőrzőlista hozzáadása", "add-checklist-item": "Elem hozzáadása az ellenőrzőlistához", @@ -97,22 +104,24 @@ "and-n-other-card": "És __count__ egyéb kártya", "and-n-other-card_plural": "És __count__ egyéb kártya", "apply": "Alkalmaz", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Mozgatás az archívumba", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archiválás", - "archived-boards": "Boards in Archive", + "app-is-offline": "Betöltés... kérem várjon!\nHa közben frissíti az oldalt, adatok veszhetnek el.\n(Ha nem töltődik be az oldal, ellenőrizze fut-e a szerver.)", + "archive": "Archiváld", + "archive-all": "Mindet archiváld", + "archive-board": "Archiváld a Táblát", + "archive-card": "Archiváld a Kártyát", + "archive-list": "Archiváld a Listát", + "archive-swimlane": "Archiváld az Úszósávot", + "archive-selection": "Archiváld a kiválasztottakat", + "archiveBoardPopup-title": "Archiváljam a táblát?", + "archived-items": "Archívum", + "archived-boards": "Táblák az Archívumban", "restore-board": "Tábla visszaállítása", - "no-archived-boards": "No Boards in Archive.", + "no-archived-boards": "Nincsenek Táblák az Archívumban", "archives": "Archiválás", - "template": "Template", - "templates": "Templates", + "template": "Sablon", + "templates": "Sablonok", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Tag hozzárendelése", "attached": "csatolva", "attachment": "Melléklet", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Törli a mellékletet?", "attachments": "Mellékletek", "auto-watch": "Táblák automatikus megtekintése, amikor létrejönnek", - "avatar-too-big": "Az avatár túl nagy (legfeljebb 70 KB)", + "avatar-too-big": "Az avatár kép túl nagy (max 520KB lehet)", "back": "Vissza", "board-change-color": "Szín megváltoztatása", "board-nb-stars": "%s csillag", "board-not-found": "A tábla nem található", "board-private-info": "Ez a tábla legyen <strong>személyes</strong>.", "board-public-info": "Ez a tábla legyen <strong>nyilvános</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Tábla hátterének megváltoztatása", "boardChangeTitlePopup-title": "Tábla átnevezése", "boardChangeVisibilityPopup-title": "Láthatóság megváltoztatása", @@ -136,19 +146,20 @@ "boards": "Táblák", "board-view": "Tábla nézet", "board-view-cal": "Naptár", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", + "board-view-swimlanes": "Úszósávok", + "board-view-collapse": "Összecsukás", + "board-view-gantt": "Gantt", "board-view-lists": "Listák", "bucket-example": "Mint például „Bakancslista”", "cancel": "Mégse", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "Ezt a Kártyát archiválták", + "board-archived": "Ezt a Táblát archiválták", "card-comments-title": "Ez a kártya %s hozzászólást tartalmaz.", "card-delete-notice": "A törlés végleges. Az összes műveletet elveszíti, amely ehhez a kártyához tartozik.", "card-delete-pop": "Az összes művelet el lesz távolítva a tevékenységlistából, és nem lesz képes többé újra megnyitni a kártyát. Nincs visszaállítási lehetőség.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "Esedékes", - "card-due-on": "Esedékes ekkor", + "card-delete-suggest-archive": "Archiválhatsz egy Kártyát, hogy eltávolítsd a Tábláról de megőrizd a vele kapcsolatos tevékenységeket.", + "card-due": "Határidő", + "card-due-on": "A határidő lejár", "card-spent": "Eltöltött idő", "card-edit-attachments": "Mellékletek szerkesztése", "card-edit-custom-fields": "Egyéni mezők szerkesztése", @@ -161,18 +172,52 @@ "cardAttachmentsPopup-title": "Innen csatolva", "cardCustomField-datePopup-title": "Dátum megváltoztatása", "cardCustomFieldsPopup-title": "Egyéni mezők szerkesztése", - "cardDeletePopup-title": "Törli a kártyát?", + "cardStartVotingPopup-title": "Szavazás indítása", + "positiveVoteMembersPopup-title": "Indítványozók", + "negativeVoteMembersPopup-title": "Ellenzők", + "card-edit-voting": "Szavazás szerkesztése", + "editVoteEndDatePopup-title": "Lejárati idő megváltoztatása", + "allowNonBoardMembers": "Minden bejelentkezett felhasználót engedj", + "vote-question": "Szavazási kérdés", + "vote-public": "Mutasd, mi szavazott mire", + "vote-for-it": "mellette", + "vote-against": "ellene", + "deleteVotePopup-title": "Törlöd a szavazást?", + "vote-delete-pop": "A Törlés végleges. Minden eseményt elvesztesz, ami ehhez kapcsolódik.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Törlöd a kártyát?", "cardDetailsActionsPopup-title": "Kártyaműveletek", "cardLabelsPopup-title": "Címkék", "cardMembersPopup-title": "Tagok", "cardMorePopup-title": "Több", - "cardTemplatePopup-title": "Create template", + "cardTemplatePopup-title": "Sablon létrehozása", "cards": "Kártyák", "cards-count": "Kártyák", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", + "cards-count-one": "Kártya", + "casSignIn": "Bejelentkezés CAS metódussal", + "cardType-card": "Kártya", + "cardType-linkedCard": "Kapcsolt Kártya", + "cardType-linkedBoard": "Kapcsolt Tábla", "change": "Változtatás", "change-avatar": "Avatár megváltoztatása", "change-password": "Jelszó megváltoztatása", @@ -184,52 +229,53 @@ "changePermissionsPopup-title": "Jogosultságok megváltoztatása", "changeSettingsPopup-title": "Beállítások megváltoztatása", "subtasks": "Alfeladat", - "checklists": "Ellenőrzőlisták", - "click-to-star": "Kattintson a tábla csillagozásához.", - "click-to-unstar": "Kattintson a tábla csillagának eltávolításához.", + "checklists": "Ellenőrző-listák", + "click-to-star": "Kattints a tábla csillagozásához.", + "click-to-unstar": "Kattints a tábla csillagának eltávolításához.", "clipboard": "Vágólap vagy fogd és vidd", "close": "Bezárás", "close-board": "Tábla bezárása", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-board-pop": "Vissza tudod állítani a Táblát, ha az \"Archív\" gombra kattintasz a fejlécnél.", + "close-card": "Close Card", "color-black": "fekete", "color-blue": "kék", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "karmazsin", + "color-darkgreen": "sötétzöld", + "color-gold": "arany", + "color-gray": "szürke", "color-green": "zöld", - "color-indigo": "indigo", + "color-indigo": "indigó", "color-lime": "citrus", - "color-magenta": "magenta", + "color-magenta": "bíborvörös", "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-navy": "sötétkék", "color-orange": "narancssárga", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "halvány türkíz", + "color-peachpuff": "barack", "color-pink": "rózsaszín", - "color-plum": "plum", + "color-plum": "szilva", "color-purple": "lila", "color-red": "piros", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "nyeregbarna", + "color-silver": "ezüst", "color-sky": "égszínkék", - "color-slateblue": "slateblue", - "color-white": "white", + "color-slateblue": "palakék", + "color-white": "fehér", "color-yellow": "sárga", - "unset-color": "Unset", + "unset-color": "Nincs megadva", "comment": "Megjegyzés", "comment-placeholder": "Megjegyzés írása", "comment-only": "Csak megjegyzés", "comment-only-desc": "Csak megjegyzést írhat a kártyákhoz.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "no-comments": "Nincs megjegyzés", + "no-comments-desc": "Nem láthatsz hozzászólásokat és eseményeket", + "worker": "Dolgozó", + "worker-desc": "Csak mozgathat Kártyákat, hozzárendelheti magát Kártyákhoz és megjegyzésekhez", "computer": "Számítógép", - "confirm-subtask-delete-dialog": "Biztosan törölni szeretnél az alfeladatot?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "confirm-subtask-delete-dialog": "Biztosan törlöd az alfeladatot?", + "confirm-checklist-delete-dialog": "Biztosan törlöd a Feladatlistát?", "copy-card-link-to-clipboard": "Kártya hivatkozásának másolása a vágólapra", - "linkCardPopup-title": "Link Card", + "linkCardPopup-title": "Kártyára hivatkozás", "searchElementPopup-title": "Keresés", "copyCardPopup-title": "Kártya másolása", "copyChecklistToManyCardsPopup-title": "Ellenőrzőlista sablon másolása több kártyára", @@ -244,11 +290,13 @@ "current": "jelenlegi", "custom-field-delete-pop": "Nincs visszavonás. Ez el fogja távolítani az egyéni mezőt az összes kártyáról, és megsemmisíti az előzményeit.", "custom-field-checkbox": "Jelölőnégyzet", + "custom-field-currency": "Pénznem", + "custom-field-currency-option": "Pénz kód", "custom-field-date": "Dátum", "custom-field-dropdown": "Legördülő lista", "custom-field-dropdown-none": "(nincs)", "custom-field-dropdown-options": "Lista lehetőségei", - "custom-field-dropdown-options-placeholder": "Nyomja meg az Enter billentyűt több lehetőség hozzáadásához", + "custom-field-dropdown-options-placeholder": "Nyomd meg az Enter billentyűt több lehetőség hozzáadásához", "custom-field-dropdown-unknown": "(ismeretlen)", "custom-field-number": "Szám", "custom-field-text": "Szöveg", @@ -257,8 +305,8 @@ "decline": "Elutasítás", "default-avatar": "Alapértelmezett avatár", "delete": "Törlés", - "deleteCustomFieldPopup-title": "Törli az egyéni mezőt?", - "deleteLabelPopup-title": "Törli a címkét?", + "deleteCustomFieldPopup-title": "Törlöd az egyéni mezőt?", + "deleteLabelPopup-title": "Törlöd a címkét?", "description": "Leírás", "disambiguateMultiLabelPopup-title": "Címkeművelet egyértelműsítése", "disambiguateMultiMemberPopup-title": "Tagművelet egyértelműsítése", @@ -271,7 +319,7 @@ "edit-wip-limit": "WIP korlát szerkesztése", "soft-wip-limit": "Gyenge WIP korlát", "editCardStartDatePopup-title": "Kezdődátum megváltoztatása", - "editCardDueDatePopup-title": "Esedékesség dátumának megváltoztatása", + "editCardDueDatePopup-title": "Határidő dátumának megváltoztatása", "editCustomFieldPopup-title": "Mező szerkesztése", "editCardSpentTimePopup-title": "Eltöltött idő megváltoztatása", "editLabelPopup-title": "Címke megváltoztatása", @@ -290,67 +338,97 @@ "email-resetPassword-text": "Kedves __user__!\n\nA jelszava visszaállításához egyszerűen kattintson a lenti hivatkozásra.\n\n__url__\n\nKöszönjük.", "email-sent": "E-mail elküldve", "email-verifyEmail-subject": "Igazolja vissza az e-mail címét a következő oldalon: __siteName__", - "email-verifyEmail-text": "Kedves __user__!\n\nAz e-mail fiókjának visszaigazolásához egyszerűen kattintson a lenti hivatkozásra.\n\n__url__\n\nKöszönjük.", + "email-verifyEmail-text": "Kedves __user__!\n\nAz e-mail fiók visszaigazolásához egyszerűen kattints a lenti hivatkozásra.\n\n__url__\n\nKöszönjük.", "enable-wip-limit": "WIP korlát engedélyezése", "error-board-doesNotExist": "Ez a tábla nem létezik", "error-board-notAdmin": "A tábla adminisztrátorának kell lennie, hogy ezt megtehesse", "error-board-notAMember": "A tábla tagjának kell lennie, hogy ezt megtehesse", "error-json-malformed": "A szöveg nem érvényes JSON", "error-json-schema": "A JSON adatok nem a helyes formátumban tartalmazzák a megfelelő információkat", + "error-csv-schema": "Ez a CSV (Vesszővel Elválasztott Értékek) /vagy/ TSV (Tabulátorral Elválasztott Értékek) anyagod nem tartalmaz megfelelő információt, vagy nem a megfelelő formátumban van.", "error-list-doesNotExist": "Ez a lista nem létezik", "error-user-doesNotExist": "Ez a felhasználó nem létezik", - "error-user-notAllowSelf": "Nem hívhatja meg saját magát", + "error-user-notAllowSelf": "Nem hívhatod meg saját magadat", "error-user-notCreated": "Ez a felhasználó nincs létrehozva", "error-username-taken": "Ez a felhasználónév már foglalt", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Az e-mail már foglalt", "export-board": "Tábla exportálása", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "export-board-json": "Exportáld a táblát JSON formátumba", + "export-board-csv": "Exportáld a táblát CSV (vesszővel elválasztott) formátumba", + "export-board-tsv": "Exportáld a táblát TSV (tabulátorral elválasztott) formátumba", + "export-board-excel": "Exportáld a Táblát Excelbe", + "user-can-not-export-excel": "Felhasználó nem tud Excelbe exportálni", + "export-board-html": "Exportáld a táblát HTML (webes) formátumba", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Tábla exportálása", + "exportCardPopup-title": "Export card", + "sort": "Rendezés", + "sort-desc": "Kattints a lista rendezéséhez", + "list-sort-by": "Rendezd a Listát e szerint:", + "list-label-modifiedAt": "Utolsó hozzáférés ideje", + "list-label-title": "Lista neve", + "list-label-sort": "Egyéni rendezés", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Szűrő", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", + "filter-cards": "Kártyák vagy Listák szűrése", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Cím alapján szűrd a Listákat", "filter-clear": "Szűrő törlése", + "filter-labels-label": "Címke alapján szűrés", "filter-no-label": "Nincs címke", + "filter-member-label": "Tagok szerinti szűrés", "filter-no-member": "Nincs tag", + "filter-assignee-label": "Hozzárendelt Tag szerinti szűrés", + "filter-no-assignee": "Nincs hozzárendelt", + "filter-custom-fields-label": "Egyedi mező szerinti szűrés", "filter-no-custom-fields": "Nincsenek egyéni mezők", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", + "filter-show-archive": "Mutasd az archivált Listákat", + "filter-hide-empty": "Rejtsd el az üres Listákat", "filter-on": "Szűrő bekapcsolva", - "filter-on-desc": "A kártyaszűrés be van kapcsolva ezen a táblán. Kattintson ide a szűrő szerkesztéséhez.", + "filter-on-desc": "A kártyaszűrés be van kapcsolva ezen a táblán. Kattints ide a szűrő szerkesztéséhez.", "filter-to-selection": "Szűrés a kijelöléshez", + "other-filters-label": "Más szűrők", "advanced-filter-label": "Speciális szűrő", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "advanced-filter-description": "A speciális szűrőben megadhatsz egy olyan szöveget, mely az alábbi operátorokat tartalmazza:\n<= >= && || ( )\nSzóközzel választhatod el az operátorokat.\nSzűrhetsz az minden egyedi mezőre a nevére és értékére hivatkozva. \nPl.: Mező1 == Érték1\nMegjegyzés: Ha a mezők vagy értékek szóközöket is tartalmaznak, idézőjelek közé kell zárnod. \nPl.: 'Mező 1' == 'Érték 1'\nKontroll-karaktereket ( ' \\ / ) vissza-perjellel írhatsz: \\' \\\\ \\/ Pl.: Johnny\\'s\nTovábbá kombinálhatsz több feltételt. Pl.: F1 == V1 || F1 == V2\nAlapesetben az ellenőrzés sorrendje: Jobbról->Balra. Ezt felülbírálhatod zárójelekkel. Pl.: F1 == V1 && ( F2 == V2 || F2 == V3 )\nRegex keresést is használhatsz: F1 == /Tes.*/i", "fullname": "Teljes név", "header-logo-title": "Vissza a táblák oldalára.", "hide-system-messages": "Rendszerüzenetek elrejtése", "headerBarCreateBoardPopup-title": "Tábla létrehozása", "home": "Kezdőlap", "import": "Importálás", + "impersonate-user": "Felhasználó személytelenítése", "link": "Link", "import-board": "tábla importálása", "import-board-c": "Tábla importálása", "import-board-title-trello": "Tábla importálása a Trello oldalról", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Az importált tábla törölni fogja a táblán lévő összes meglévő adatot, és kicseréli az importált táblával.", + "import-board-title-wekan": "Tábla importálás korábbi exportból", + "import-board-title-csv": "Importálás CSV / TSV -ből", "from-trello": "A Trello oldalról", - "from-wekan": "From previous export", - "import-board-instruction-trello": "A Trello tábláján menjen a „Menü”, majd a „Több”, „Nyomtatás és exportálás”, „JSON exportálása” menüpontokra, és másolja ki az eredményül kapott szöveget.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "from-wekan": "Előző exportból", + "from-csv": "CSV / TSV -ből", + "import-board-instruction-trello": "A Trello táblán a „Menü” >> majd a „Több” >> „Nyomtatás és exportálás” >> „JSON exportálása” menüpontnál másold ki az eredményül kapott szöveget.", + "import-board-instruction-csv": "Illeszd be a CSV (vesszővel elválasztott) vagy TSV (tabulátorral elválasztott) szöveget.", + "import-board-instruction-wekan": "A Tábládban válaszd a \"Menü\", aztán az \"Tábla Exportálás\" pontot, majd másold a szöveget a letöltött fájlba.", + "import-board-instruction-about-errors": "Ha hibákat ír Tábla importálásakor, attól még néha működik, csak a \"Minden Tábla\" alá sorolódik.", "import-json-placeholder": "Illessze be ide az érvényes JSON adatokat", + "import-csv-placeholder": "CSV / TSV adat beillesztés ide", "import-map-members": "Tagok leképezése", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map": "Az importált Táblának vannak felhasználói.\nVálaszd ki, kikkel szeretnéd összerendelni.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Tagok leképezésének vizsgálata", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", + "import-user-select": "Válassz egy helyettes felhasználót a meglévők közül", + "importMapMembersAddPopup-title": "Válassz Tagot", "info": "Verzió", "initials": "Kezdőbetűk", "invalid-date": "Érvénytelen dátum", @@ -361,7 +439,7 @@ "keyboard-shortcuts": "Gyorsbillentyűk", "label-create": "Címke létrehozása", "label-default": "%s címke (alapértelmezett)", - "label-delete-pop": "Nincs visszavonás. Ez el fogja távolítani ezt a címkét az összes kártyáról, és törli az előzményeit.", + "label-delete-pop": "Nincs visszavonás. Ez el fogja távolítani ezt a Címkét az összes Kártyáról, és törli az előzményeit.", "labels": "Címkék", "language": "Nyelv", "last-admin-desc": "Nem változtathatja meg a szerepeket, mert legalább egy adminisztrátora szükség van.", @@ -369,21 +447,25 @@ "leave-board-pop": "Biztosan el szeretné hagyni ezt a táblát: __boardTitle__? El lesz távolítva a táblán lévő összes kártyáról.", "leaveBoardPopup-title": "Elhagyja a táblát?", "link-card": "Összekapcsolás ezzel a kártyával", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-archive-cards": "Archiváld a Lista összes Kártyáját", + "list-archive-cards-pop": "Most minden Kártyát eltávolítasz a Tábláról. Hogy láthasd az archiváltakat, vagy visszahelyezd ide, kattints a \"Menü\" > \"Archívum\" -ra.", "list-move-cards": "A listán lévő összes kártya áthelyezése", "list-select-cards": "A listán lévő összes kártya kiválasztása", - "set-color-list": "Set Color", + "set-color-list": "Szín megadása", "listActionPopup-title": "Műveletek felsorolása", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", + "settingsUserPopup-title": "Felhasználói beállítások", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Úszósáv Műveletek", + "swimlaneAddPopup-title": "Új Úszósáv hozzáadása ez alá", "listImportCardPopup-title": "Trello kártya importálása", + "listImportCardsTsvPopup-title": "Excel CSV/TSV importálása", "listMorePopup-title": "Több", "link-list": "Összekapcsolás ezzel a listával", "list-delete-pop": "Az összes művelet el lesz távolítva a tevékenységlistából, és nem lesz lehetősége visszaállítani a listát. Nincs visszavonás.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "list-delete-suggest-archive": "Archiválhatsz egy Listát a Tábláról és megőrizheted a vele kapcsolatos korábbi eseményeket.", "lists": "Listák", - "swimlanes": "Swimlanes", + "swimlanes": "Úszósávok", "log-out": "Kijelentkezés", "log-in": "Bejelentkezés", "loginPopup-title": "Bejelentkezés", @@ -396,14 +478,16 @@ "moveCardToTop-title": "Áthelyezés a tetejére", "moveSelectionPopup-title": "Kijelölés áthelyezése", "multi-selection": "Többszörös kijelölés", + "multi-selection-label": "Címke hozzáadása a kiválasztottakhoz", + "multi-selection-member": "Tag hozzáadása a kiválasztottakhoz", "multi-selection-on": "Többszörös kijelölés bekapcsolva", "muted": "Némítva", "muted-info": "Soha sem lesz értesítve a táblán lévő semmilyen változásról.", "my-boards": "Saját tábláim", "name": "Név", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", + "no-archived-cards": "Nincs Kártya az Archívumban", + "no-archived-lists": "Nincs Lista az Archívumban", + "no-archived-swimlanes": "Nincs Úszósáv az Archívumban", "no-results": "Nincs találat", "normal": "Normál", "normal-desc": "Megtekintheti és szerkesztheti a kártyákat. Nem változtathatja meg a beállításokat.", @@ -439,13 +523,14 @@ "restore": "Visszaállítás", "save": "Mentés", "search": "Keresés", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "keresőkifejezés", + "rules": "Szabályok", + "search-cards": "Keresés Kártya/Lista címekben, Leírásokban, és Egyedi mezőkben ezen Táblán belül", + "search-example": "Gépeld be a keresendő szöveget és nyomj Enter billentyűt", "select-color": "Szín kiválasztása", + "select-board": "Válassz Táblát", "set-wip-limit-value": "Korlát beállítása a listán lévő feladatok legnagyobb számához", "setWipLimitPopup-title": "WIP korlát beállítása", - "shortcut-assign-self": "Önmaga hozzárendelése a jelenlegi kártyához", + "shortcut-assign-self": "Önmagad hozzárendelése a jelenlegi kártyához", "shortcut-autocomplete-emoji": "Emodzsi automatikus kiegészítése", "shortcut-autocomplete-members": "Tagok automatikus kiegészítése", "shortcut-clear-filters": "Összes szűrő törlése", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Kártyáim szűrése", "shortcut-show-shortcuts": "A hivatkozási lista előre hozása", "shortcut-toggle-filterbar": "Szűrő oldalsáv ki- és bekapcsolása", + "shortcut-toggle-searchbar": "Kereső oldalsáv ki- és bekapcsolása", "shortcut-toggle-sidebar": "Tábla oldalsáv ki- és bekapcsolása", "show-cards-minimum-count": "Kártyaszámok megjelenítése, ha a lista többet tartalmaz mint", "sidebar-open": "Oldalsáv megnyitása", @@ -469,21 +555,29 @@ "overtime-hours": "Túlóra (óra)", "overtime": "Túlóra", "has-overtime-cards": "Van túlórás kártyája", - "has-spenttime-cards": "Has spent time cards", + "has-spenttime-cards": "Van eltöltött-idő kártyája", "time": "Idő", "title": "Cím", "tracking": "Követés", - "tracking-info": "Értesítve lesz az összes olyan kártya változásáról, amelyen létrehozóként vagy tagként vesz részt.", + "tracking-info": "Értesítve leszel az összes olyan kártya változásáról, amelyen létrehozóként vagy tagként veszel részt.", "type": "Típus", "unassign-member": "Tag hozzárendelésének megszüntetése", - "unsaved-description": "Van egy mentetlen leírása.", + "unsaved-description": "Van egy mentetlen leírásod.", "unwatch": "Megfigyelés megszüntetése", "upload": "Feltöltés", "upload-avatar": "Egy avatár feltöltése", "uploaded-avatar": "Egy avatár feltöltve", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Felhasználónév", + "import-usernames": "Import Usernames", "view-it": "Megtekintés", - "warn-list-archived": "warning: this card is in an list at Archive", + "warn-list-archived": "figyelem: ez a Kártya egy archivált Listában van benne", "watch": "Megfigyelés", "watching": "Megfigyelés", "watching-info": "Értesítve lesz a táblán lévő összes változásról", @@ -491,13 +585,13 @@ "welcome-swimlane": "1. mérföldkő", "welcome-list1": "Alapok", "welcome-list2": "Speciális", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", + "card-templates-swimlane": "Kártya Sablonok", + "list-templates-swimlane": "Lista Sablonok", + "board-templates-swimlane": "Tábla Sablonok", "what-to-do": "Mit szeretne tenni?", "wipLimitErrorPopup-title": "Érvénytelen WIP korlát", "wipLimitErrorPopup-dialog-pt1": "A listán lévő feladatok száma magasabb a meghatározott WIP korlátnál.", - "wipLimitErrorPopup-dialog-pt2": "Helyezzen át néhány feladatot a listáról, vagy állítson be magasabb WIP korlátot.", + "wipLimitErrorPopup-dialog-pt2": "Helyezz át néhány feladatot a listáról, vagy állíts be magasabb WIP korlátot.", "admin-panel": "Adminisztrációs panel", "settings": "Beállítások", "people": "Emberek", @@ -518,20 +612,20 @@ "send-from": "Feladó", "send-smtp-test": "Teszt e-mail küldése magamnak", "invitation-code": "Meghívási kód", - "email-invite-register-subject": "__inviter__ egy meghívás küldött Önnek", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-invite-register-subject": "__inviter__ egy meghívás küldött Neked", + "email-invite-register-text": "Kedves __user__,\n\n__inviter__ meghív téged egy kanban táblára, hogy együtt dolgozhassatok.\n\nKérlek kattints az alábbi linkre:\n__url__\n\nA meghívás kódja: __icode__\n\nSzép napot :-)", "email-smtp-test-subject": "SMTP Test Email", "email-smtp-test-text": "Sikeresen elküldött egy e-mailt", "error-invitation-code-not-exist": "A meghívási kód nem létezik", "error-notAuthorized": "Nincs jogosultsága az oldal megtekintéséhez.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", + "webhook-title": "Webhurok neve", + "webhook-token": "Token hitelesítéshez (opcionális)", "outgoing-webhooks": "Kimenő webhurkok", - "bidirectional-webhooks": "Two-Way Webhooks", + "bidirectional-webhooks": "Két-irányú webhurkok", "outgoingWebhooksPopup-title": "Kimenő webhurkok", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", + "boardCardTitlePopup-title": "Kártya-cím filter", + "disable-webhook": "Tiltsd le ezt a webhurkot", + "global-webhook": "Globális webhurkok", "new-outgoing-webhook": "Új kimenő webhurok", "no-name": "(Ismeretlen)", "Node_version": "Node verzió", @@ -548,19 +642,21 @@ "OS_Totalmem": "Operációs rendszer összes memóriája", "OS_Type": "Operációs rendszer típusa", "OS_Uptime": "Operációs rendszer üzemideje", - "days": "days", + "days": "nap", "hours": "óra", "minutes": "perc", "seconds": "másodperc", - "show-field-on-card": "A mező megjelenítése a kártyán", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", + "show-field-on-card": "A mező megjelenítése a Kártyán", + "automatically-field-on-card": "Mező hozzáadása új Kártyákhoz", + "always-field-on-card": "Mező hozzáadása minden Kártyához", + "showLabel-field-on-card": "Mutassa a Címkék nevét a mini Kártyákon is", "yes": "Igen", "no": "Nem", "accounts": "Fiókok", "accounts-allowEmailChange": "E-mail megváltoztatásának engedélyezése", "accounts-allowUserNameChange": "Felhasználónév megváltoztatásának engedélyezése", "createdAt": "Létrehozva", + "modifiedAt": "Módosult", "verified": "Ellenőrizve", "active": "Aktív", "card-received": "Érkezett", @@ -569,126 +665,131 @@ "card-end-on": "Befejeződik ekkor", "editCardReceivedDatePopup-title": "Érkezési dátum megváltoztatása", "editCardEndDatePopup-title": "Befejezési dátum megváltoztatása", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", + "setCardColorPopup-title": "Szín beállítása", + "setCardActionsColorPopup-title": "Válassz színt", + "setSwimlaneColorPopup-title": "Válassz színt", + "setListColorPopup-title": "Válassz színt", + "assigned-by": "Hozzárendelte ", + "requested-by": "Igényelte", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "A törlés végleges. Minden Lista, Kártya és kapcsolódó esemény megsemmisül ezen a Táblán.", + "delete-board-confirm-popup": "Minden Lista, Kártya, Címke és Esemény véglegesen törlésre kerül és nincs rá mód, hogy visszanyerd a Tábla tartalmát. Nincs visszavonási lehetőség sem.", + "boardDeletePopup-title": "TÖRLÖD a Táblát?", + "delete-board": "Tábla törlése", + "default-subtasks-board": "Al-feladat ehhez a Táblához: __board__", + "default": "Alapértelmezett", + "queue": "Feladat sor", "subtask-settings": "Alfeladat beállítások", - "card-settings": "Card Settings", + "card-settings": "Kátya beállítások", "boardSubtaskSettingsPopup-title": "Tábla alfeladat beállítások", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", + "boardCardSettingsPopup-title": "Kátya beállítások", + "deposit-subtasks-board": "Helyezd az al-feladatokat erre a Táblára:", "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", + "show-parent-in-minicard": "Mutasd a szülőt a mini-kártyán:", "prefix-with-full-path": "Prefix with full path", "prefix-with-parent": "Prefix with parent", "subtext-with-full-path": "Subtext with full path", "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "change-card-parent": "Szülő cseréje", + "parent-card": "Szülő Kártya", + "source-board": "Forrás Tábla", + "no-parent": "Ne mutasd a szülőt", + "activity-added-label": "hozzáadta ezt a Címkét: \"%s\" ehhez: %s", + "activity-removed-label": "eltávolította ezt a Címkét: \"%s\" innen: %s", + "activity-delete-attach": "eltávolított egy csatolmányt innen: %s", + "activity-added-label-card": "hozzáadott egy Címkét: \"%s\"", + "activity-removed-label-card": "eltávolította ezt a Címkét: \"%s\"", + "activity-delete-attach-card": "törölt egy csatolmányt", + "activity-set-customfield": "beállított egy egyedi mezőt: \"%s\" erre: \"%s\" itt: %s", "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Archívumba helyezve", - "r-unarchived": "Helyreállítva az archívumból", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Mozgatás az archívumba", - "r-unarchive": "Helyreállítás az archívumból", - "r-card": "card", - "r-add": "Hozzáadás", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", + "r-rule": "Szabály", + "r-add-trigger": "Új kiváltó ok", + "r-add-action": "Új cselekmény", + "r-board-rules": "Tábla szabályok", + "r-add-rule": "Új szabály", + "r-view-rule": "Szabály megtekintése", + "r-delete-rule": "Töröld a szabályt", + "r-new-rule-name": "Új szabály fejléc", + "r-no-rules": "Nincsenek szabályok", + "r-trigger": "Kiváltó ok", + "r-action": "Művelet", + "r-when-a-card": "Amikor a Kártya", + "r-is": "..", + "r-is-moved": "mozgatva lett", + "r-added-to": "Hozzáadták", + "r-removed-from": "Eltávolították innen", + "r-the-board": "a Tábla", + "r-list": "Lista", + "list": "Lista", + "set-filter": "Szűrő beállítása", + "r-moved-to": "Áttették ide:", + "r-moved-from": "Áttették innen:", + "r-archived": "Archívumba helyezték", + "r-unarchived": "Helyreállították az archívumból", + "r-a-card": "egy Kártya", + "r-when-a-label-is": "Amikor a Címke ", + "r-when-the-label": "Amikor ez a címke:", + "r-list-name": "Lista neve", + "r-when-a-member": "Amikor egy Tag", + "r-when-the-member": "Amikor ez a Tag:", + "r-name": "név", + "r-when-a-attach": "Amikor a csatolmány", + "r-when-a-checklist": "Amikor egy feladat-lista", + "r-when-the-checklist": "Amikor ez a feladatlista:", + "r-completed": "Kész", + "r-made-incomplete": "Mégsem kész", + "r-when-a-item": "Amikor egy feladat-lista elem", + "r-when-the-item": "Amikor ez a feladat-lista elem:", + "r-checked": "Be lett pipálva", + "r-unchecked": "Pipátlanítva lett", + "r-move-card-to": "Helyezd át a Kártyát ide:", + "r-top-of": "Teteje", + "r-bottom-of": "Alja", + "r-its-list": "Listája", + "r-archive": "Mozgasd az archívumba", + "r-unarchive": "Állítsd helyre az archívumból", + "r-card": "Kártya", + "r-add": "Add hozzá", + "r-remove": "Távolítsd el", + "r-label": "címke", + "r-member": "tag", + "r-remove-all": "Távolíts el minden Felhasználót a Kártyáról", + "r-set-color": "Állítsd a színt erre:", + "r-checklist": "feladat-lista", + "r-check-all": "Összes megjelölése", + "r-uncheck-all": "Összes jelölés törlése", + "r-items-check": "feladat-lista elemei", + "r-check": "Bepipál", + "r-uncheck": "Pipátlanít", + "r-item": "elem", "r-of-checklist": "ellenőrzőlistából", - "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Elem ellenőrzése", - "r-d-uncheck-one": "Uncheck item", + "r-send-email": "E-mail küldése", + "r-to": "címzett", + "r-of": "of", + "r-subject": "tárgy", + "r-rule-details": "A Szabály részletei", + "r-d-move-to-top-gen": "Tedd a Kártyát a Listája tetejére", + "r-d-move-to-top-spec": "Tedd a Kártyát ennek a Listának a tetejére:", + "r-d-move-to-bottom-gen": "Tedd a Kártyát a Listája aljára", + "r-d-move-to-bottom-spec": "Tedd a Kártyát ennek a Listának az aljára:", + "r-d-send-email": "E-mail küldése", + "r-d-send-email-to": "címzett", + "r-d-send-email-subject": "tárgy", + "r-d-send-email-message": "üzenet", + "r-d-archive": "Archiváld a Kártyát", + "r-d-unarchive": "Állítsd helyre a kártyát az Archívumból", + "r-d-add-label": "Adj hozzá Címkét", + "r-d-remove-label": "Távolítsd el a Címkét", + "r-create-card": "Készíts új Kártyát", + "r-in-list": "ebben a listában", + "r-in-swimlane": "ezen az Úszósávon", + "r-d-add-member": "Adj hozzá Felhasználót", + "r-d-remove-member": "Távolítsd el ezt a felhasználót", + "r-d-remove-all-member": "Távolíts el minden felhasználót", + "r-d-check-all": "Pipálj ki minden elemet a feladat-listán", + "r-d-uncheck-all": "Pipátlaníts ki minden elemet a feladat-listán", + "r-d-check-one": "Elem kijelölése", + "r-d-uncheck-one": "Elem bejelölésének törlése", "r-d-check-of-list": "ellenőrzőlistából", "r-d-add-checklist": "Ellenőrzőlista hozzáadása", "r-d-remove-checklist": "Ellenőrzőlista eltávolítása", @@ -702,12 +803,12 @@ "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "Amikor egy kártya másik listába kerül", "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", + "r-update": "Frissítés", + "r-datefield": "dátum mező", + "r-df-start-at": "kezdet", + "r-df-due-at": "határidős", + "r-df-end-at": "végzett", + "r-df-received-at": "megérkezett", "r-to-current-datetime": "to current date/time", "r-remove-value-from": "Remove value from", "ldap": "LDAP", @@ -725,45 +826,237 @@ "display-authentication-method": "Hitelelesítési mód mutatása", "default-authentication-method": "Alapértelmezett hitelesítési mód", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", + "restore-all": "Összes visszaállítása", + "delete-all": "Összes törlése", + "loading": "Betöltés folyamatban, kis türelmet…", "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-dueAt": "megváltoztatta a *határidő* dátumát erre:\n__timeValue__ az új dátum\n__timeOldValue__ volt a régi\nEzen a *Kártyán*: __card__", "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", + "a-dueAt": "átírta a *határidő* dátumát erre:", "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "almostdue": "a határidő dátuma %s közeledik", + "pastdue": "a határidő dátuma %s már lejárt", + "duenow": "a határidő dátuma %s ma van", + "act-newDue": "__list__/__card__ *Kártyának* van az első *határidő* dátum figyelmeztetése ezen a Táblán: [__board__]", + "act-withDue": "__list__/__card__ Kártyának határidős dátum-figyelmeztetései ezen a Táblán: [__board__]", + "act-almostdue": "emlékeztette a mostani határidő (__timeValue__) közeledtére ezen a Kártyán: __card__", + "act-pastdue": "emlékeztette a mostani határidő (__timeValue__) elmúltára ezen a Kártyán: __card__", + "act-duenow": "emlékeztette, hogy a mostani határidő (__timeValue__) ma van ezen a Kártyán: __card__", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", + "hide-minicard-label-text": "Rejtse el a Címke szövegét a mini Kártyákon", "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", + "assignee": "Felelős", + "cardAssigneesPopup-title": "Felelős", "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", + "new": "Új", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Felhasználó szerkesztése", + "newUserPopup-title": "Új felhasználó", + "notifications": "Értesítések", + "view-all": "Összes megtekintése", "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", + "mark-all-as-read": "Összes megjelölése olvasottként", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Állítsa be a hét kezdetének napját", + "monday": "hétfő", + "tuesday": "kedd", + "wednesday": "szerda", + "thursday": "csütörtök", + "friday": "péntek", + "saturday": "szombat", + "sunday": "vasárnap", + "status": "Állapot", + "swimlane": "Úszósáv", + "owner": "Tulajdonos", + "last-modified-at": "Utoljára módosítva ", + "last-activity": "Utolsó tevékenység", + "voting": "Szavazás", + "archived": "Archiválva", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Kijelölt elemek elrejtése", + "task": "Feladat", + "create-task": "Új feladat", + "ok": "Rendben", + "organizations": "Szervezetek", + "teams": "Csoportok", + "displayName": "Megjelenítendő név", + "shortName": "Rövid név", + "website": "Weboldal", + "person": "Személy", + "my-cards": "Kártyáim", + "card": "Kártya", + "board": "Tábla", + "context-separator": "/", + "myCardsSortChange-title": "Kártyáim rendezése", + "myCardsSortChangePopup-title": "Kártyáim rendezése", + "myCardsSortChange-choice-board": "Tábla szerint", + "myCardsSortChange-choice-dueat": "Határidő szerint", + "dueCards-title": "Határidős Kártyák", + "dueCardsViewChange-title": "Határidős Kártyák Nézete", + "dueCardsViewChangePopup-title": "Határidős Kártyák Nézete", + "dueCardsViewChange-choice-me": "Én", + "dueCardsViewChange-choice-all": "Minden felhasználó", + "dueCardsViewChange-choice-all-description": "Minden befejezetlen kártyát felsorol \"határidős\" dátummal, amihez a felhasználónak hozzáférése van.", + "broken-cards": "Sérült Kártyák", + "board-title-not-found": "\"%s\" nevű Tábla nem található", + "swimlane-title-not-found": "\"%s\" nevű Úszósáv nem található", + "list-title-not-found": "\"%s\" nevű Lista nem található.", + "label-not-found": "Ez a Címke nem található: \"%s\"", + "label-color-not-found": "Ez a Címke-szín nem található: %s", + "user-username-not-found": "\"%s\" nevű felhasználó nem található", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Minden Táblán keress", + "no-cards-found": "Nem találtam Kártyát", + "one-card-found": "Egy Kártyát találtam", + "n-cards-found": "%s Kártyát találtam", + "n-n-of-n-cards-found": "Találat: __start__-__end__ összesen: __total__ Kártyából", + "operator-board": "Tábla", + "operator-board-abbrev": "b", + "operator-swimlane": "Úszósáv", + "operator-swimlane-abbrev": "s", + "operator-list": "Lista", + "operator-list-abbrev": "l", + "operator-label": "Címke", + "operator-label-abbrev": "#", + "operator-user": "Felhasználó", + "operator-user-abbrev": "@", + "operator-member": "tag", + "operator-member-abbrev": "m", + "operator-assignee": "hozzárendelő", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "állapot", + "operator-due": "határidő", + "operator-created": "létrehozva", + "operator-modified": "módosítva", + "operator-sort": "rendezés", + "operator-comment": "megjegyzés", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archiválva", + "predicate-open": "open", + "predicate-ended": "befejezve", + "predicate-all": "összes", + "predicate-overdue": "késésben", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "határidős", + "predicate-modified": "módosítva", + "predicate-created": "létrehozva", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "feladat-lista", + "predicate-start": "kezdet", + "predicate-end": "végzett", + "predicate-assignee": "hozzárendelő", + "predicate-member": "tag", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s nem egy művelet", + "operator-number-expected": "Ez a művelet: __operator__ egy számot vár, de ezt kapta: \"__value__\"", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Jegyzetek", + "globalSearch-instructions-heading": "Keresési Utasítások", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "A szöveges keresések immunisak a kis-nagybetűkre.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link ehhez a kereséshez", + "excel-font": "Arial", + "number": "Szám", + "label-colors": "Címke színek", + "label-names": "Címke feliratok", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/hy.i18n.json b/i18n/hy.i18n.json index 452995376..c006dcbd4 100644 --- a/i18n/hy.i18n.json +++ b/i18n/hy.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/id.i18n.json b/i18n/id.i18n.json index f84f4a8c7..8f68ceb70 100644 --- a/i18n/id.i18n.json +++ b/i18n/id.i18n.json @@ -1,15 +1,15 @@ { "accept": "Terima", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-activity-notify": "Pemberitahuan Aktifitas", + "act-addAttachment": "menambahkan lampiran __attachment__ ke kartu __card__ pada daftar __list__ pada jalur __swimline__ dalam papan __board__", + "act-deleteAttachment": "menghapus lampiran __attachment__ di kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-addSubtask": "menambah tugas __subtask__ ke kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-addLabel": "Menambah label __label__ ke kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-addedLabel": "Menambah label __label__ ke kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-removeLabel": "Menghapus label __label__ dari kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-removedLabel": "Menghapus label __label__ dari kartu__card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-addChecklist": "menambah daftar cek __checklist__ ke kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", + "act-addChecklistItem": "menambah item cek __checklistItem__ ke daftar cek __checklist__ di kartu __card__ pada daftar __list__ pada jalur __swimlane__ dalam papan __board__", "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -25,7 +25,7 @@ "act-createCustomField": "created custom field __customField__ at board __board__", "act-deleteCustomField": "deleted custom field __customField__ at board __board__", "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", + "act-createList": "ditambahkan daftar __list__ pada papan __board__", "act-addBoardMember": "added member __member__ to board __board__", "act-archivedBoard": "Board __board__ moved to Archive", "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -98,7 +105,7 @@ "and-n-other-card_plural": "Dan__menghitung__kartu lain", "apply": "Terapkan", "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", + "archive": "Pindahlan ke Arsip", "archive-all": "Move All to Archive", "archive-board": "Move Board to Archive", "archive-card": "Move Card to Archive", @@ -108,11 +115,13 @@ "archiveBoardPopup-title": "Move Board to Archive?", "archived-items": "Arsip", "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", + "restore-board": "Pulihkan Papan", + "no-archived-boards": "Tidak ada Papan pada Arsip", "archives": "Arsip", - "template": "Template", - "templates": "Templates", + "template": "Klise", + "templates": "Klise", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Tugaskan anggota", "attached": "terlampir", "attachment": "Lampiran", @@ -120,33 +129,35 @@ "attachmentDeletePopup-title": "Hapus Lampiran?", "attachments": "Daftar Lampiran", "auto-watch": "Otomatis diawasi saat membuat Panel", - "avatar-too-big": "Berkas avatar terlalu besar (70KB maks)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Kembali", "board-change-color": "Ubah warna", "board-nb-stars": "%s bintang", "board-not-found": "Panel tidak ditemukan", "board-private-info": "Panel ini akan jadi <strong>Pribadi<strong>", "board-public-info": "Panel ini akan jadi <strong>Publik<strong", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Ubah Warna Latar ", "boardChangeTitlePopup-title": "Ganti Nama Panel", "boardChangeVisibilityPopup-title": "Ubah Penampakan", "boardChangeWatchPopup-title": "Ubah Pengamatan", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardMenuPopup-title": "Pengaturan Papan", + "boardChangeViewPopup-title": "Tampilan Papan", "boards": "Panel", - "board-view": "Board View", - "board-view-cal": "Calendar", + "board-view": "Tampilan Papan", + "board-view-cal": "Kalender", "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", + "board-view-collapse": "Ciutkan", + "board-view-gantt": "Gantt", "board-view-lists": "Daftar", "bucket-example": "Contohnya seperti “Bucket List” ", "cancel": "Batal", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "Kartu ini telah dipindahkan ke Arsip", + "board-archived": "Kartu ini telah dipindahkan ke Arsip", "card-comments-title": "Kartu ini punya %s komentar", "card-delete-notice": "Menghapus sama dengan permanen. Anda akan kehilangan semua aksi yang terhubung ke kartu ini", "card-delete-pop": "Semua aksi akan dihapus dari aktivitas dan anda tidak bisa lagi buka kartu ini", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-delete-suggest-archive": "Kamu bisa memindahkan Kartu ke Arsip untuk menghapusnya dari Papan dan mempertahankan Aktifitas", "card-due": "Jatuh Tempo", "card-due-on": "Jatuh Tempo pada", "card-spent": "Spent Time", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Lampirkan dari", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Hapus kartu", "cardDetailsActionsPopup-title": "Aksi Kartu", "cardLabelsPopup-title": "Daftar Label", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Daftar Kartu", "cards-count": "Daftar Kartu", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Tutup", "close-board": "Tutup Panel", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "hitam", "color-blue": "biru", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "sekarang", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Tanggal", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Anda harus jadi member panel ini untuk melakukannya", "error-json-malformed": "Teks Anda bukan JSON yang sah", "error-json-schema": "Data JSON Anda tidak mengikutsertakan informasi yang sesuai format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Daftar ini tidak ada", "error-user-doesNotExist": "Nama pengguna ini tidak ada", "error-user-notAllowSelf": "Anda tidak bisa mengundang diri sendiri", "error-user-notCreated": "Nama pengguna ini tidak dibuat", "error-username-taken": "Nama pengguna ini sudah dipakai", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Exspor Panel", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exspor Panel", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Penyaringan", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Bersihkan penyaringan", + "filter-labels-label": "Filter by label", "filter-no-label": "Tidak ada label", + "filter-member-label": "Filter by member", "filter-no-member": "Tidak ada anggota", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Penyaring aktif", "filter-on-desc": "Anda memfilter kartu di panel ini. Klik di sini untuk menyunting filter", "filter-to-selection": "Saring berdasarkan yang dipilih", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nama Lengkap", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Buat Panel", "home": "Beranda", "import": "Impor", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Impor panel dari Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "Di panel Trello anda, ke 'Menu', terus 'More', 'Print and Export','Export JSON', dan salin hasilnya", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Tempelkan data JSON yang sah disini", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Petakan partisipan", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review pemetaan partisipan", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Pilih semua kartu di daftar ini", "set-color-list": "Set Color", "listActionPopup-title": "Daftar Tindakan", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Impor dari Kartu Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Lainnya", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,13 +478,15 @@ "moveCardToTop-title": "Pindahkan ke atas", "moveSelectionPopup-title": "Pindahkan yang dipilih", "multi-selection": "Multi Pilihan", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi Pilihan aktif", "muted": "Pemberitahuan tidak aktif", "muted-info": "Anda tidak akan pernah dinotifikasi semua perubahan di panel ini", "my-boards": "Panel saya", "name": "Nama", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", + "no-archived-cards": "Tidak ada kartu di arsip.", + "no-archived-lists": "Tidak ada daftar di arsip.", "no-archived-swimlanes": "No swimlanes in Archive.", "no-results": "Tidak ada hasil", "normal": "Normal", @@ -428,8 +512,8 @@ "quick-access-description": "Beri bintang panel untuk menambah shortcut di papan ini", "remove-cover": "Hapus Sampul", "remove-from-board": "Hapus dari panel", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", + "remove-label": "Hapus Label", + "listDeletePopup-title": "Hapus Daftar ?", "remove-member": "Hapus Anggota", "remove-member-from-card": "Hapus dari Kartu", "remove-member-pop": "Hapus__nama__(__username__) dari __boardTitle__? Partisipan akan dihapus dari semua kartu di panel ini. Mereka akan diberi tahu", @@ -439,21 +523,23 @@ "restore": "Pulihkan", "save": "Simpan", "search": "Cari", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", + "rules": "Peraturan", + "search-cards": "Cari dari judul kartu/daftar, deskripsi dan bidang khusus di papan ini", + "search-example": "Write text you search and press Enter", + "select-color": "Pilih Warna", + "select-board": "Select Board", + "set-wip-limit-value": "Tetapkan batas untuk jumlah tugas maksimum dalam daftar ini", + "setWipLimitPopup-title": "Tetapkan Batas WIP ", "shortcut-assign-self": "Masukkan diri anda sendiri ke kartu ini", - "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-emoji": "Pelengkap Otomatis emoji", "shortcut-autocomplete-members": "Autocomplete partisipan", "shortcut-clear-filters": "Bersihkan semua saringan", "shortcut-close-dialog": "Tutup Dialog", "shortcut-filter-my-cards": "Filter kartu saya", "shortcut-show-shortcuts": "Angkat naik shortcut daftar ini", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "shortcut-toggle-filterbar": "Toggle Filter Bilah Samping", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Papan Bilah Samping", "show-cards-minimum-count": "Tampilkan jumlah kartu jika daftar punya lebih dari ", "sidebar-open": "Buka Sidebar", "sidebar-close": "Tutup Sidebar", @@ -465,23 +551,31 @@ "team": "Tim", "this-board": "Panel ini", "this-card": "Kartu ini", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", + "spent-time-hours": "Waktu yang dihabiskan (jam)", + "overtime-hours": "Lembur (jam)", + "overtime": "Lembur", + "has-overtime-cards": "Punya kartu lembur", "has-spenttime-cards": "Has spent time cards", "time": "Waktu", "title": "Judul", "tracking": "Pelacakan", "tracking-info": "Anda akan dinotifikasi semua perubahan di kartu tersebut diaman anda terlibat sebagai creator atau partisipan", - "type": "Type", + "type": "tipe", "unassign-member": "Tidak sertakan partisipan", "unsaved-description": "Anda memiliki deskripsi yang belum disimpan.", "unwatch": "Tidak mengamati", "upload": "Unggah", "upload-avatar": "Unggah avatar", "uploaded-avatar": "Avatar diunggah", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Nama Pengguna", + "import-usernames": "Import Usernames", "view-it": "Lihat", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Amati", @@ -491,11 +585,11 @@ "welcome-swimlane": "Milestone 1", "welcome-list1": "Tingkat dasar", "welcome-list2": "Tingkat lanjut", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", + "card-templates-swimlane": "Klise Kartu", + "list-templates-swimlane": "Klise Daftar", + "board-templates-swimlane": "Klise Papan", "what-to-do": "Apa yang mau Anda lakukan?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-title": "Batas WIP tidak valid", "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", "admin-panel": "Panel Admin", @@ -520,11 +614,11 @@ "invitation-code": "Kode Undangan", "email-invite-register-subject": "__inviter__ mengirim undangan ke Anda", "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-subject": "Tes Surel SMTP", "email-smtp-test-text": "You have successfully sent an email", "error-invitation-code-not-exist": "Kode undangan tidak ada", "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", + "webhook-title": "Nama Webhook", "webhook-token": "Token (Optional for Authentication)", "outgoing-webhooks": "Outgoing Webhooks", "bidirectional-webhooks": "Two-Way Webhooks", @@ -534,10 +628,10 @@ "global-webhook": "Global Webhooks", "new-outgoing-webhook": "New Outgoing Webhook", "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", + "Node_version": "Versi Node", + "Meteor_version": "Versi Meteor", + "MongoDB_version": "Versi MongoDB", + "MongoDB_storage_engine": "Mesin penyimpanan MongoDb", "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", "OS_Arch": "OS Arch", "OS_Cpus": "OS CPU Count", @@ -548,44 +642,47 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", + "days": "hari", + "hours": "m", + "minutes": "menit", + "seconds": "detik", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", + "yes": "Ya", + "no": "Tidak", + "accounts": "Akun", "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", + "createdAt": "Dibuat pada", + "modifiedAt": "Modified at", + "verified": "Terverifikasi", + "active": "Aktif", + "card-received": "Diterima", + "card-received-on": "Diterima pada", + "card-end": "Berakhir", + "card-end-on": "Berakhir pada", + "editCardReceivedDatePopup-title": "Ubah tanggal diterima", + "editCardEndDatePopup-title": "Ubah tanggal berakhir", + "setCardColorPopup-title": "Tetapkan warna", + "setCardActionsColorPopup-title": "Pilih warna", + "setSwimlaneColorPopup-title": "Pilih warna", + "setListColorPopup-title": "Pilih warna", + "assigned-by": "Ditandatangani Oleh", + "requested-by": "Diminta Oleh", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", + "boardDeletePopup-title": "Hapus Papan?", + "delete-board": "Hapus Papan", "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", + "default": "Standar", + "queue": "Antrian", "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", + "card-settings": "Pengaturan Kartu", "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", + "boardCardSettingsPopup-title": "Pengaturan Kartu", "deposit-subtasks-board": "Deposit subtasks to this board:", "deposit-subtasks-list": "Landing list for subtasks deposited here:", "show-parent-in-minicard": "Show parent in minicard:", @@ -605,34 +702,37 @@ "activity-delete-attach-card": "deleted an attachment", "activity-set-customfield": "set custom field '%s' to '%s' in %s", "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", + "r-rule": "Aturan", + "r-add-trigger": "Tambahkan pelatuk", + "r-add-action": "Tambahkan aksi", + "r-board-rules": "Aturan papan", + "r-add-rule": "Tambahkan aturan", + "r-view-rule": "Lihat aturan", "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "Ketika kartu", + "r-is": "adalah", + "r-is-moved": "dipindahkan", + "r-added-to": "Ditambahkan ke", + "r-removed-from": "Dihapus dari", + "r-the-board": "papan", + "r-list": "daftar", + "list": "List", + "set-filter": "Atur Saringan", + "r-moved-to": "Dipindahkan ke", + "r-moved-from": "Dipindahkan dari", + "r-archived": "Dipindahkan ke Arsip", + "r-unarchived": "Dipulihkan dari Arsip", + "r-a-card": "satu kartu", + "r-when-a-label-is": "Ketika label adalah", + "r-when-the-label": "Ketika label", + "r-list-name": "nama daftar", + "r-when-a-member": "Ketika anggota adalah", + "r-when-the-member": "Ketika anggota", + "r-name": "nama", "r-when-a-attach": "When an attachment", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", @@ -645,16 +745,16 @@ "r-move-card-to": "Move card to", "r-top-of": "Top of", "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", + "r-its-list": "daftarnya", + "r-archive": "Pindahkan ke Arsip", + "r-unarchive": "Pulihkan dari Arsip", + "r-card": "Kartu", "r-add": "Tambah", - "r-remove": "Remove", + "r-remove": "hapus", "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", + "r-member": "anggota", + "r-remove-all": "Hapus semua anggota dari kartu", + "r-set-color": "Tetapkan warna ke", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", @@ -664,74 +764,77 @@ "r-item": "item", "r-of-checklist": "of checklist", "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", + "r-to": "kepada", + "r-of": "dari", + "r-subject": "subyek", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", "r-d-move-to-top-spec": "Move card to top of list", "r-d-move-to-bottom-gen": "Move card to bottom of its list", "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", + "r-d-send-email": "Kirim surel", + "r-d-send-email-to": "kepada", + "r-d-send-email-subject": "subyek", + "r-d-send-email-message": "pesan", + "r-d-archive": "Pindahkan kartu ke Arsip", + "r-d-unarchive": "Pulihkan kartu dari Arsip", "r-d-add-label": "Tambahkan label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", + "r-d-remove-label": "Hapus label", + "r-create-card": "Buat kartu baru", + "r-in-list": "pada daftar", + "r-in-swimlane": "pada jalur", + "r-d-add-member": "Tambahkan anggota", + "r-d-remove-member": "Hapus anggota", + "r-d-remove-all-member": "Hapus semua anggota", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", "r-d-check-one": "Check item", "r-d-uncheck-one": "Uncheck item", "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", + "r-d-add-checklist": "Tambahkan daftar periksa", + "r-d-remove-checklist": "Hapus daftar periksa", + "r-by": "oleh", + "r-add-checklist": "Tambahkan daftar periksa", + "r-with-items": "dengan item", + "r-items-list": "item1, item2, item3", "r-add-swimlane": "Add swimlane", "r-swimlane-name": "swimlane name", "r-board-note": "Note: leave a field empty to match every possible value.", "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-set": "Tetapkan", + "r-update": "Ubah", + "r-datefield": "kolom tanggal", + "r-df-start-at": "mulai", + "r-df-due-at": "sampai", + "r-df-end-at": "berakhir", + "r-df-received-at": "diterima", + "r-to-current-datetime": "ke tanggal/waktu sekarang", + "r-remove-value-from": "Hapus nilai dari", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", "authentication-method": "Metode Autentikasi", "authentication-type": "Tipe Autentikasi", "custom-product-name": "Custom Product Name", - "layout": "Layout", + "layout": "Tata Letak", "hide-logo": "Sembunyikan Logo", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", + "add-custom-html-after-body-start": "Tambahkan HTML khusus setelah <body> mulai", + "add-custom-html-before-body-end": "Tambahkan HTML khusus sebelum </body> berakhir", + "error-undefined": "Ada yang salah", + "error-ldap-login": "Terjadi kesalahan saat mencoba masuk", "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", + "duplicate-board": "Duplikat Papan", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "Jumlah orang:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", + "restore-all": "Pulihkan semua", + "delete-all": "Hapus semua", + "loading": "Sedang memuat, harap tunggu.", + "previous_as": "terakhir kali adalah", "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", @@ -750,20 +853,210 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "hide-minicard-label-text": "Sembunyikan teks label kartu mini", + "show-desktop-drag-handles": "Tampilkan gagang seret desktop", + "assignee": "Penerima tugas", + "cardAssigneesPopup-title": "Penerima tugas", + "addmore-detail": "Tambahkan deskripsi yang lebih rinci", + "show-on-card": "Tampilkan pada Kartu", + "new": "Baru", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Ubah Pengguna", + "newUserPopup-title": "Pengguna Baru", + "notifications": "Pemberitahuan", + "view-all": "Lihat Semua", + "filter-by-unread": "Saring yang Belum Dibaca", + "mark-all-as-read": "Tandai semua telah dibaca", + "remove-all-read": "Hapus semua yang telah dibaca", + "allow-rename": "Ijinkan Ganti Nama", + "allowRenamePopup-title": "Ijinkan Ganti Nama", + "start-day-of-week": "Tetapkan hari dimulai dalam minggu", + "monday": "Senin", + "tuesday": "Selasa", + "wednesday": "Rabu", + "thursday": "Kamis", + "friday": "Jum'at", + "saturday": "Sabtu", + "sunday": "Minggu", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Pemilik", + "last-modified-at": "Terakhir diubah pada", + "last-activity": "Aktifitas terakhir", + "voting": "Pemungutan Suara", + "archived": "Diarsipkan", + "delete-linked-card-before-this-card": "Kamu tidak dapat menghapus kartu ini sebelum menghapus kartu tertaut yang telah", + "delete-linked-cards-before-this-list": "Kamu tidak dapat menghapus daftar ini sebelum menghapus kartu tertaut yang mengarah ke kartu dalam daftar ini", + "hide-checked-items": "Sembunyikan item yang dicentang", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "daftar", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "anggota", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "sampai", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "sampai", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "mulai", + "predicate-end": "berakhir", + "predicate-assignee": "assignee", + "predicate-member": "anggota", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ig.i18n.json b/i18n/ig.i18n.json index fda6037b2..683ce5eb1 100644 --- a/i18n/ig.i18n.json +++ b/i18n/ig.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Aha", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "Hụ ya", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Hụ", @@ -553,7 +647,8 @@ "minutes": "nkeji", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Ee", "no": "Mba", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Ekere na", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/it.i18n.json b/i18n/it.i18n.json index ba283e2b7..d40ce3cef 100644 --- a/i18n/it.i18n.json +++ b/i18n/it.i18n.json @@ -1,769 +1,1062 @@ { - "accept": "Accept", - "act-activity-notify": "Notifica attività", - "act-addAttachment": "aggiunto allegato __attachment__ alla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-deleteAttachment": "eliminato allegato __attachment__ dalla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-addSubtask": "aggiunto sottotask __subtask__ alla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-addLabel": "aggiunta etichetta __label__ alla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-addedLabel": "aggiunta etichetta __label__ alla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-removeLabel": "rimossa etichetta __label__ dalla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-removedLabel": "rimossa etichetta __label__ dalla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-addChecklist": "aggiunta lista di controllo __label__ alla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-addChecklistItem": "aggiunto elemento __checklistItem__ alla lista di controllo __checklist__ della scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-removeChecklist": "rimossa lista di controllo __checklist__ dalla scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-removeChecklistItem": "rimosso elemento __checklistitem__ dalla lista di controllo __checkList__ della scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "act-checkedItem": "attivato __checklistitem__ nella lista di controllo __checklist__ della scheda __card__ della lista __list__ della corsia __swimlane__ nella bacheca __board__", - "act-uncheckedItem": "disattivato __checklistItem__ della lista di controllo __checklist__ dalla scheda __card__ della lista __list__ della corsia __swimlane__ nella bacheca __board__", - "act-completeChecklist": "completata lista di controllo __checklist__ nella scheda __card__ della lista __list__ della corsia __swimlane__ nella bacheca __board__", - "act-uncompleteChecklist": "lista di controllo __checklist__ incompleta nella scheda __card__ della lista __list__ in corsia __swimlane__ della bacheca __board__", - "act-addComment": "commento sulla scheda __card__: __comment__ nella lista __list__ della corsia __swimlane__ della bacheca __board__", - "act-editComment": "commento modificato sulla scheda __card__: __commento__ nella lista __list__ nella swim lane __swimlane__ nella bacheca __board__", - "act-deleteComment": "commento eliminato sulla scheda __card__: __commento__ nella lista __list__ nella swim lane__ swimlane__ nella bacheca __board__", - "act-createBoard": "bacheca __board__ creata", - "act-createSwimlane": "creata corsia __swimlane__ alla bacheca __board__", - "act-createCard": "scheda __card__ creata nella lista __list__ della corsia __swimlane__ della bacheca __board__", - "act-createCustomField": "creato campo personalizzato __customField__ nella bacheca __board", - "act-deleteCustomField": "campo personalizzato eliminato __customField__ nella bacheca __board", - "act-setCustomField": "campo personalizzato modificato __customField__: __customFieldValue__ sulla scheda __card__ sulla lista __list__ sulla swimlane __swimlane__ sulla bacheca __board__", - "act-createList": "aggiunta lista __list__ alla bacheca __board__", - "act-addBoardMember": "aggiunto membro __member__ alla bacheca __board__", - "act-archivedBoard": "Bacheca __board__ archiviata", - "act-archivedCard": "Scheda __card__ della lista __list__ della corsia __swimlane__ della bacheca __board__ archiviata", - "act-archivedList": "Lista __list__ della corsia __swimlane__ della bacheca __board__ archiviata", - "act-archivedSwimlane": "Corsia __swimlane__ della bacheca __board__ archiviata", - "act-importBoard": "Bacheca __board__ importata", - "act-importCard": "scheda importata __card__ nella lista __list__ della corsia __swimlane__ della bacheca __board__", - "act-importList": "lista __list__ importata nella corsia __swimlane__ della bacheca __board__", - "act-joinMember": "aggiunto membro __member__ alla scheda __card__ della list __list__ nella corsia __swimlane__ della bacheca __board__", - "act-moveCard": "spostata scheda __card__ della bacheca __board__ dalla lista __oldList__ della corsia __oldSwimlane__ alla lista __list__ della corsia __swimlane__", - "act-moveCardToOtherBoard": "postata scheda __card__ dalla lista __oldList__ della corsia __oldSwimlane__ della bacheca __oldBoard__ alla lista __list__ nella corsia __swimlane__ della bacheca __board__", - "act-removeBoardMember": "rimosso membro __member__ dalla bacheca __board__", - "act-restoredCard": "scheda ripristinata __card__ della lista __list__ nella corsia __swimlane__ della bacheca __board__", - "act-unjoinMember": "rimosso membro __member__ dalla scheda __card__ della lista __list__ della corsia __swimlane__ nella bacheca __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Azioni", - "activities": "Attività", - "activity": "Attività", - "activity-added": "ha aggiunto %s a %s", - "activity-archived": "%s spostato nell'archivio", - "activity-attached": "allegato %s a %s", - "activity-created": "creato %s", - "activity-customfield-created": "%s creato come campo personalizzato", - "activity-excluded": "escluso %s da %s", - "activity-imported": "importato %s in %s da %s", - "activity-imported-board": "importato %s da %s", - "activity-joined": "si è unito a %s", - "activity-moved": "spostato %s da %s a %s", - "activity-on": "su %s", - "activity-removed": "rimosso %s da %s", - "activity-sent": "inviato %s a %s", - "activity-unjoined": "ha abbandonato %s", - "activity-subtask-added": "aggiunto il sottocompito a 1%s", - "activity-checked-item": "selezionata %s nella checklist %s di %s", - "activity-unchecked-item": "disattivato %s nella checklist %s di %s", - "activity-checklist-added": "aggiunta checklist a %s", - "activity-checklist-removed": "È stata rimossa una checklist da%s", - "activity-checklist-completed": "%s di %s checklists completate", - "activity-checklist-uncompleted": "La checklist non è stata completata", - "activity-checklist-item-added": "Aggiunto l'elemento checklist a '%s' in %s", - "activity-checklist-item-removed": "è stato rimosso un elemento della checklist da '%s' in %s", - "add": "Aggiungere", - "activity-checked-item-card": "%s è stato selezionato nella checklist %s", - "activity-unchecked-item-card": "%s è stato deselezionato nella checklist %s", - "activity-checklist-completed-card": "checklist __label__ completata nella scheda __card__ della lista __list__ della corsia __swimlane__  nella bacheca __board__", - "activity-checklist-uncompleted-card": "La checklist %s non è completa", - "activity-editComment": "commento modificato %s", - "activity-deleteComment": "commento eliminato %s", - "add-attachment": "Aggiungi Allegato", - "add-board": "Aggiungi Bacheca", - "add-card": "Aggiungi Scheda", - "add-swimlane": "Aggiungi Diagramma Swimlane", - "add-subtask": "Aggiungi sotto-compito", - "add-checklist": "Aggiungi Checklist", - "add-checklist-item": "Aggiungi un elemento alla checklist", - "add-cover": "Aggiungi copertina", - "add-label": "Aggiungi Etichetta", - "add-list": "Aggiungi Lista", - "add-members": "Aggiungi membri", - "added": "Aggiunto", - "addMemberPopup-title": "Membri", - "admin": "Amministratore", - "admin-desc": "Può vedere e modificare schede, rimuovere membri e modificare le impostazioni della bacheca.", - "admin-announcement": "Annunci", - "admin-announcement-active": "Attiva annunci di sistema", - "admin-announcement-title": "Annunci dall'Amministratore", - "all-boards": "Tutte le bacheche", - "and-n-other-card": "E __count__ altra scheda", - "and-n-other-card_plural": "E __count__ altre schede", - "apply": "Applica", - "app-is-offline": "Caricamento, attendere prego. Aggiornare la pagina porterà ad una perdita dei dati. Se il caricamento non dovesse funzionare, per favore controlla che il server non sia stato fermato.", - "archive": "Sposta nell'Archivio", - "archive-all": "Sposta tutto nell'Archivio", - "archive-board": "Sposta la bacheca nell'Archivio", - "archive-card": "Sposta la scheda nell'Archivio", - "archive-list": "Sposta elenco nell'Archivio", - "archive-swimlane": "Sposta diagramma nell'Archivio", - "archive-selection": "Sposta la selezione nell'archivio", - "archiveBoardPopup-title": "Spostare al bacheca nell'archivio?", - "archived-items": "Archivia", - "archived-boards": "Bacheche nell'archivio", - "restore-board": "Ripristina Bacheca", - "no-archived-boards": "Nessuna bacheca presente nell'archivio", - "archives": "Archivia", - "template": "Template", - "templates": "Templates", - "assign-member": "Aggiungi membro", - "attached": "allegato", - "attachment": "Allegato", - "attachment-delete-pop": "L'eliminazione di un allegato è permanente. Non è possibile annullare.", - "attachmentDeletePopup-title": "Eliminare l'allegato?", - "attachments": "Allegati", - "auto-watch": "Segui automaticamente le bacheche quando vengono create.", - "avatar-too-big": "L'avatar è troppo grande (70KB max)", - "back": "Indietro", - "board-change-color": "Cambia colore", - "board-nb-stars": "%s stelle", - "board-not-found": "Bacheca non trovata", - "board-private-info": "Questa bacheca sarà <strong>privata</strong>.", - "board-public-info": "Questa bacheca sarà <strong>pubblica</strong>.", - "boardChangeColorPopup-title": "Cambia sfondo della bacheca", - "boardChangeTitlePopup-title": "Rinomina bacheca", - "boardChangeVisibilityPopup-title": "Cambia visibilità", - "boardChangeWatchPopup-title": "Cambia faccia", - "boardMenuPopup-title": "Impostazioni bacheca", - "boardChangeViewPopup-title": "Visualizza bacheca", - "boards": "Bacheche", - "board-view": "Visualizza bacheca", - "board-view-cal": "Calendario", - "board-view-swimlanes": "Diagramma Swimlane", - "board-view-collapse": "Collassa", - "board-view-lists": "Liste", - "bucket-example": "Per esempio come \"una lista di cose da fare\"", - "cancel": "Cancella", - "card-archived": "Questa scheda è stata spostata nell'archivio", - "board-archived": "Questa bacheca è stata spostata nell'archivio", - "card-comments-title": "Questa scheda ha %s commenti.", - "card-delete-notice": "L'eliminazione è permanente. Tutte le azioni associate a questa scheda andranno perse.", - "card-delete-pop": "Tutte le azioni saranno rimosse dal flusso attività e non sarai in grado di riaprire la scheda. Non potrai tornare indietro.", - "card-delete-suggest-archive": "Puoi spostare una scheda nell'archivio per rimuoverla dalla bacheca e mantenere la sua attività", - "card-due": "Scadenza", - "card-due-on": "Scade", - "card-spent": "Tempo trascorso", - "card-edit-attachments": "Modifica allegati", - "card-edit-custom-fields": "Modifica campo personalizzato", - "card-edit-labels": "Modifica etichette", - "card-edit-members": "Modifica membri", - "card-labels-title": "Cambia le etichette per questa scheda.", - "card-members-title": "Aggiungi o rimuovi membri della bacheca da questa scheda", - "card-start": "Inizio", - "card-start-on": "Inizia", - "cardAttachmentsPopup-title": "Allega da", - "cardCustomField-datePopup-title": "Cambia data", - "cardCustomFieldsPopup-title": "Modifica campo personalizzato", - "cardDeletePopup-title": "Elimina scheda?", - "cardDetailsActionsPopup-title": "Azioni scheda", - "cardLabelsPopup-title": "Etichette", - "cardMembersPopup-title": "Membri", - "cardMorePopup-title": "Altro", - "cardTemplatePopup-title": "Crea un template", - "cards": "Schede", - "cards-count": "Schede", - "casSignIn": "Entra con CAS", - "cardType-card": "Scheda", - "cardType-linkedCard": "Scheda collegata", - "cardType-linkedBoard": "Bacheca collegata", - "change": "Cambia", - "change-avatar": "Cambia avatar", - "change-password": "Cambia password", - "change-permissions": "Cambia permessi", - "change-settings": "Cambia impostazioni", - "changeAvatarPopup-title": "Cambia avatar", - "changeLanguagePopup-title": "Cambia lingua", - "changePasswordPopup-title": "Cambia password", - "changePermissionsPopup-title": "Cambia permessi", - "changeSettingsPopup-title": "Cambia impostazioni", - "subtasks": "Sotto-compiti", - "checklists": "Checklist", - "click-to-star": "Clicca per stellare questa bacheca", - "click-to-unstar": "Clicca per togliere la stella da questa bacheca", - "clipboard": "Clipboard o drag & drop", - "close": "Chiudi", - "close-board": "Chiudi bacheca", - "close-board-pop": "Potrai ripristinare la bacheca cliccando sul tasto \"Archivio\" presente nell'intestazione della home.", - "color-black": "nero", - "color-blue": "blu", - "color-crimson": "Rosso cremisi", - "color-darkgreen": "Verde scuro", - "color-gold": "Dorato", - "color-gray": "Grigio", - "color-green": "verde", - "color-indigo": "Indaco", - "color-lime": "lime", - "color-magenta": "Magenta", - "color-mistyrose": "Mistyrose", - "color-navy": "Navy", - "color-orange": "arancione", - "color-paleturquoise": "Turchese chiaro", - "color-peachpuff": "Pesca", - "color-pink": "rosa", - "color-plum": "Prugna", - "color-purple": "viola", - "color-red": "rosso", - "color-saddlebrown": "Saddlebrown", - "color-silver": "Argento", - "color-sky": "azzurro", - "color-slateblue": "Ardesia", - "color-white": "Bianco", - "color-yellow": "giallo", - "unset-color": "Non impostato", - "comment": "Commento", - "comment-placeholder": "Scrivi Commento", - "comment-only": "Solo commenti", - "comment-only-desc": "Puoi commentare solo le schede.", - "no-comments": "Non ci sono commenti.", - "no-comments-desc": "Impossibile visualizzare commenti o attività.", - "worker": "Lavoratore", - "worker-desc": "Può solo spostare schede, assegnarsi una scheda e commentare.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Sei sicuro di voler eliminare il sotto-compito?", - "confirm-checklist-delete-dialog": "Sei sicuro di voler eliminare la checklist?", - "copy-card-link-to-clipboard": "Copia link della scheda sulla clipboard", - "linkCardPopup-title": "Collega scheda", - "searchElementPopup-title": "Cerca", - "copyCardPopup-title": "Copia Scheda", - "copyChecklistToManyCardsPopup-title": "Copia template checklist su più schede", - "copyChecklistToManyCardsPopup-instructions": "Titolo e la descrizione della scheda di destinazione in questo formato JSON", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titolo prima scheda\", \"description\":\"Descrizione prima scheda\"}, {\"title\":\"Titolo seconda scheda\",\"description\":\"Descrizione seconda scheda\"},{\"title\":\"Titolo ultima scheda\",\"description\":\"Descrizione ultima scheda\"} ]", - "create": "Crea", - "createBoardPopup-title": "Crea bacheca", - "chooseBoardSourcePopup-title": "Importa bacheca", - "createLabelPopup-title": "Crea etichetta", - "createCustomField": "Crea campo", - "createCustomFieldPopup-title": "Crea campo", - "current": "corrente", - "custom-field-delete-pop": "Non potrai tornare indietro. Questa azione rimuoverà questo campo personalizzato da tutte le schede ed eliminerà ogni sua traccia.", - "custom-field-checkbox": "Casella di scelta", - "custom-field-date": "Data", - "custom-field-dropdown": "Lista a discesa", - "custom-field-dropdown-none": "(niente)", - "custom-field-dropdown-options": "Lista opzioni", - "custom-field-dropdown-options-placeholder": "Premi invio per aggiungere altre opzioni", - "custom-field-dropdown-unknown": "(sconosciuto)", - "custom-field-number": "Numero", - "custom-field-text": "Testo", - "custom-fields": "Campi personalizzati", - "date": "Data", - "decline": "Declina", - "default-avatar": "Avatar predefinito", - "delete": "Elimina", - "deleteCustomFieldPopup-title": "Elimina il campo personalizzato?", - "deleteLabelPopup-title": "Eliminare etichetta?", - "description": "Descrizione", - "disambiguateMultiLabelPopup-title": "Disambiguare l'azione Etichetta", - "disambiguateMultiMemberPopup-title": "Disambiguare l'azione Membro", - "discard": "Scarta", - "done": "Fatto", - "download": "Download", - "edit": "Modifica", - "edit-avatar": "Cambia avatar", - "edit-profile": "Modifica profilo", - "edit-wip-limit": "Modifica limite di work in progress", - "soft-wip-limit": "Limite Work in progress soft", - "editCardStartDatePopup-title": "Cambia data di inizio", - "editCardDueDatePopup-title": "Cambia data di scadenza", - "editCustomFieldPopup-title": "Modifica campo", - "editCardSpentTimePopup-title": "Cambia tempo trascorso", - "editLabelPopup-title": "Cambia etichetta", - "editNotificationPopup-title": "Modifica notifiche", - "editProfilePopup-title": "Modifica profilo", - "email": "Email", - "email-enrollAccount-subject": "Creato un account per te su __siteName__", - "email-enrollAccount-text": "Ciao __user__,\n\nPer iniziare ad usare il servizio, clicca sul link seguente:\n\n__url__\n\nGrazie.", - "email-fail": "Invio email fallito", - "email-fail-text": "Errore nel tentativo di invio email", - "email-invalid": "Email non valida", - "email-invite": "Invita via email", - "email-invite-subject": "__inviter__ ti ha inviato un invito", - "email-invite-text": "Caro __user__,\n\n__inviter__ ti ha invitato ad unirti alla bacheca \"__board__\" per le collaborazioni.\n\nPer favore clicca sul link seguente:\n\n__url__\n\nGrazie.", - "email-resetPassword-subject": "Ripristina la tua password su on __siteName__", - "email-resetPassword-text": "Ciao __user__,\n\nPer ripristinare la tua password, clicca sul link seguente:\n\n__url__\n\nGrazie.", - "email-sent": "Email inviata", - "email-verifyEmail-subject": "Verifica il tuo indirizzo email su on __siteName__", - "email-verifyEmail-text": "Ciao __user__,\n\nPer verificare il tuo account email, clicca sul link seguente:\n\n__url__\n\nGrazie.", - "enable-wip-limit": "Abilita limite di work in progress", - "error-board-doesNotExist": "Questa bacheca non esiste", - "error-board-notAdmin": "Devi essere admin di questa bacheca per poterlo fare", - "error-board-notAMember": "Devi essere un membro di questa bacheca per poterlo fare", - "error-json-malformed": "Il tuo testo non è un JSON valido", - "error-json-schema": "Il tuo file JSON non contiene le giuste informazioni nel formato corretto", - "error-list-doesNotExist": "Questa lista non esiste", - "error-user-doesNotExist": "Questo utente non esiste", - "error-user-notAllowSelf": "Non puoi invitare te stesso", - "error-user-notCreated": "L'utente non è stato creato", - "error-username-taken": "Questo username è già utilizzato", - "error-email-taken": "L'email è già stata presa", - "export-board": "Esporta bacheca", - "sort": "Ordina", - "sort-desc": "Clicca per ordinare la lista", - "list-sort-by": "Ordina la lista per:", - "list-label-modifiedAt": "Orario ultimo accesso", - "list-label-title": "Nome della lista", - "list-label-sort": "Il tuo ordine manuale", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filtra", - "filter-cards": "Filtra schede o liste", - "list-filter-label": "Filtra lista per titolo", - "filter-clear": "Pulisci filtri", - "filter-no-label": "Nessuna etichetta", - "filter-no-member": "Nessun membro", - "filter-no-custom-fields": "Nessun campo personalizzato", - "filter-show-archive": "Mostra le liste archiviate", - "filter-hide-empty": "Nascondi liste vuote", - "filter-on": "Il filtro è attivo", - "filter-on-desc": "Stai filtrando le schede su questa bacheca. Clicca qui per modificare il filtro,", - "filter-to-selection": "Seleziona", - "advanced-filter-label": "Filtro avanzato", - "advanced-filter-description": "Il filtro avanzato permette di scrivere una stringa contenente i seguenti operatori: == != <= >= && || ( ) Uno spazio è usato come separatore tra gli operatori. Si può filtrare per tutti i campi personalizzati, scrivendo i loro nomi e valori. Per esempio: Campo1 == Valore1. Nota: Se i campi o i valori contengono spazi, è necessario incapsularli all'interno di paici singoli. Per esempio: 'Campo 1' == 'Valore 1'. Per i singoli caratteri di controllo (' V) che devono essere ignorati, si può usare \\. Per esempio: C1 == Campo1 == L'\\ho. Si possono anche combinare condizioni multiple. Per esempio: C1 == V1 || C1 ==V2. Di norma tutti gli operatori vengono lettti da sinistra a destra. Si può cambiare l'ordine posizionando delle parentesi. Per esempio: C1 == V1 && ( C2 == V2 || C2 == V3) . Inoltre è possibile cercare nei campi di testo usando le espressioni regolari (n.d.t. regex): F1 ==/Tes.*/i", - "fullname": "Nome completo", - "header-logo-title": "Torna alla tua bacheca.", - "hide-system-messages": "Nascondi i messaggi di sistema", - "headerBarCreateBoardPopup-title": "Crea bacheca", - "home": "Home", - "import": "Importa", - "link": "Collegamento", - "import-board": "Importa bacheca", - "import-board-c": "Importa bacheca", - "import-board-title-trello": "Importa una bacheca da Trello", - "import-board-title-wekan": "Importa bacheca dall'esportazione precedente", - "import-sandstorm-backup-warning": "Non cancellare i dati che importi dalla bacheca esportata in origine o da Trello prima che il controllo finisca e si riapra ancora, altrimenti otterrai un messaggio di errore Bacheca non trovata, che significa che i dati sono perduti.", - "import-sandstorm-warning": "La bacheca importata cancellerà tutti i dati esistenti su questa bacheca e li rimpiazzerà con quelli della bacheca importata.", - "from-trello": "Da Trello", - "from-wekan": "Dall'esportazione precedente", - "import-board-instruction-trello": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.", - "import-board-instruction-wekan": "Nella tua bacheca vai su \"Menu\", poi \"Esporta la bacheca\", e copia il testo nel file scaricato", - "import-board-instruction-about-errors": "Se hai degli errori quando importi una bacheca, qualche volta l'importazione funziona comunque, e la bacheca si trova nella pagina \"Tutte le bacheche\"", - "import-json-placeholder": "Incolla un JSON valido qui", - "import-map-members": "Mappatura dei membri", - "import-members-map": "La bacheca che hai importato contiene alcuni membri. Per favore scegli i membri che vuoi importare tra i tuoi utenti", - "import-show-user-mapping": "Rivedi la mappatura dei membri", - "import-user-select": "Scegli l'utente che vuoi venga utilizzato come questo membro", - "importMapMembersAddPopup-title": "Scegli membro", - "info": "Versione", - "initials": "Iniziali", - "invalid-date": "Data non valida", - "invalid-time": "Tempo non valido", - "invalid-user": "User non valido", - "joined": "si è unito a", - "just-invited": "Sei stato appena invitato a questa bacheca", - "keyboard-shortcuts": "Scorciatoie da tastiera", - "label-create": "Crea etichetta", - "label-default": "%s etichetta (default)", - "label-delete-pop": "Non potrai tornare indietro. Procedendo, rimuoverai questa etichetta da tutte le schede e distruggerai la sua cronologia.", - "labels": "Etichette", - "language": "Lingua", - "last-admin-desc": "Non puoi cambiare i ruoli perché deve esserci almeno un admin.", - "leave-board": "Abbandona bacheca", - "leave-board-pop": "Sei sicuro di voler abbandonare __boardTitle__? Sarai rimosso da tutte le schede in questa bacheca.", - "leaveBoardPopup-title": "Abbandona Bacheca?", - "link-card": "Link a questa scheda", - "list-archive-cards": "Sposta tutte le schede in questo elenco nell'Archivio", - "list-archive-cards-pop": "Questo rimuoverà tutte le schede nell'elenco dalla bacheca. Per vedere le schede nell'archivio e portarle dov'erano nella bacheca, clicca su \"Menu\" > \"Archivio\".", - "list-move-cards": "Sposta tutte le schede in questa lista", - "list-select-cards": "Selezione tutte le schede in questa lista", - "set-color-list": "Imposta un colore", - "listActionPopup-title": "Azioni disponibili", - "swimlaneActionPopup-title": "Azioni diagramma Swimlane", - "swimlaneAddPopup-title": "Aggiungi un diagramma Swimlane di seguito", - "listImportCardPopup-title": "Importa una scheda di Trello", - "listMorePopup-title": "Altro", - "link-list": "Link a questa lista", - "list-delete-pop": "Tutte le azioni saranno rimosse dal flusso attività e non sarai in grado di recuperare la lista. Non potrai tornare indietro.", - "list-delete-suggest-archive": "Puoi spostare un elenco nell'archivio per rimuoverlo dalla bacheca e mantentere la sua attività.", - "lists": "Liste", - "swimlanes": "Diagramma Swimlane", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Impostazioni membri", - "members": "Membri", - "menu": "Menu", - "move-selection": "Sposta selezione", - "moveCardPopup-title": "Sposta scheda", - "moveCardToBottom-title": "Sposta in fondo", - "moveCardToTop-title": "Sposta in alto", - "moveSelectionPopup-title": "Sposta selezione", - "multi-selection": "Multi-Selezione", - "multi-selection-on": "Multi-Selezione attiva", - "muted": "Silenziato", - "muted-info": "Non sarai mai notificato delle modifiche in questa bacheca", - "my-boards": "Le mie bacheche", - "name": "Nome", - "no-archived-cards": "Non ci sono schede nell'archivio.", - "no-archived-lists": "Non ci sono elenchi nell'archivio.", - "no-archived-swimlanes": "Non ci sono diagrammi Swimlane nell'archivio.", - "no-results": "Nessun risultato", - "normal": "Normale", - "normal-desc": "Può visionare e modificare le schede. Non può cambiare le impostazioni.", - "not-accepted-yet": "Invitato non ancora accettato", - "notify-participate": "Ricevi aggiornamenti per qualsiasi scheda a cui partecipi come creatore o membro", - "notify-watch": "Ricevi aggiornamenti per tutte le bacheche, liste o schede che stai seguendo", - "optional": "opzionale", - "or": "o", - "page-maybe-private": "Questa pagina potrebbe essere privata. Potresti essere in grado di vederla <a href='%s'>facendo il log-in</a>.", - "page-not-found": "Pagina non trovata.", - "password": "Password", - "paste-or-dragdrop": "per incollare, oppure trascina & rilascia il file immagine (solo immagini)", - "participating": "Partecipando", - "preview": "Anteprima", - "previewAttachedImagePopup-title": "Anteprima", - "previewClipboardImagePopup-title": "Anteprima", - "private": "Privata", - "private-desc": "Questa bacheca è privata. Solo le persone aggiunte alla bacheca possono vederla e modificarla.", - "profile": "Profilo", - "public": "Pubblica", - "public-desc": "Questa bacheca è pubblica. È visibile a chiunque abbia il link e sarà mostrata dai motori di ricerca come Google. Solo le persone aggiunte alla bacheca possono modificarla.", - "quick-access-description": "Stella una bacheca per aggiungere una scorciatoia in questa barra.", - "remove-cover": "Rimuovi cover", - "remove-from-board": "Rimuovi dalla bacheca", - "remove-label": "Rimuovi Etichetta", - "listDeletePopup-title": "Eliminare Lista?", - "remove-member": "Rimuovi utente", - "remove-member-from-card": "Rimuovi dalla scheda", - "remove-member-pop": "Rimuovere __name__ (__username__) da __boardTitle__? L'utente sarà rimosso da tutte le schede in questa bacheca. Riceveranno una notifica.", - "removeMemberPopup-title": "Rimuovere membro?", - "rename": "Rinomina", - "rename-board": "Rinomina bacheca", - "restore": "Ripristina", - "save": "Salva", - "search": "Cerca", - "rules": "Regole", - "search-cards": "Ricerca per titolo, descrizione scheda/lista e campi personalizzati su questa bacheca", - "search-example": "Testo da ricercare?", - "select-color": "Seleziona Colore", - "set-wip-limit-value": "Seleziona un limite per il massimo numero di attività in questa lista", - "setWipLimitPopup-title": "Imposta limite di work in progress", - "shortcut-assign-self": "Aggiungi te stesso alla scheda corrente", - "shortcut-autocomplete-emoji": "Autocompletamento emoji", - "shortcut-autocomplete-members": "Autocompletamento membri", - "shortcut-clear-filters": "Pulisci tutti i filtri", - "shortcut-close-dialog": "Chiudi finestra di dialogo", - "shortcut-filter-my-cards": "Filtra le mie schede", - "shortcut-show-shortcuts": "Apri questa lista di scorciatoie", - "shortcut-toggle-filterbar": "Apri/chiudi la barra laterale dei filtri", - "shortcut-toggle-sidebar": "Apri/chiudi la barra laterale della bacheca", - "show-cards-minimum-count": "Mostra il contatore delle schede se la lista ne contiene più di", - "sidebar-open": "Apri Sidebar", - "sidebar-close": "Chiudi Sidebar", - "signupPopup-title": "Crea un account", - "star-board-title": "Clicca per stellare questa bacheca. Sarà mostrata all'inizio della tua lista bacheche.", - "starred-boards": "Bacheche stellate", - "starred-boards-description": "Le bacheche stellate vengono mostrato all'inizio della tua lista bacheche.", - "subscribe": "Sottoscrivi", - "team": "Team", - "this-board": "questa bacheca", - "this-card": "questa scheda", - "spent-time-hours": "Tempo trascorso (ore)", - "overtime-hours": "Overtime (ore)", - "overtime": "Overtime", - "has-overtime-cards": "Ci sono scheda scadute", - "has-spenttime-cards": "Ci sono scheda con tempo impiegato", - "time": "Ora", - "title": "Titolo", - "tracking": "Monitoraggio", - "tracking-info": "Sarai notificato per tutte le modifiche alle schede delle quali sei creatore o membro.", - "type": "Tipo", - "unassign-member": "Rimuovi membro", - "unsaved-description": "Hai una descrizione non salvata", - "unwatch": "Non seguire", - "upload": "Upload", - "upload-avatar": "Carica un avatar", - "uploaded-avatar": "Avatar caricato", - "username": "Username", - "view-it": "Vedi", - "warn-list-archived": "Attenzione:questa scheda si trova in un elenco dell'archivio", - "watch": "Segui", - "watching": "Stai seguendo", - "watching-info": "Sarai notificato per tutte le modifiche in questa bacheca", - "welcome-board": "Bacheca di benvenuto", - "welcome-swimlane": "Pietra miliare 1", - "welcome-list1": "Basi", - "welcome-list2": "Avanzate", - "card-templates-swimlane": "Template scheda", - "list-templates-swimlane": "Elenca i template", - "board-templates-swimlane": "Bacheca dei template", - "what-to-do": "Cosa vuoi fare?", - "wipLimitErrorPopup-title": "Limite work in progress non valido.", - "wipLimitErrorPopup-dialog-pt1": "Il numero di compiti in questa lista è maggiore del limite di work in progress che hai definito in precedenza.", - "wipLimitErrorPopup-dialog-pt2": "Per favore, sposta alcuni dei compiti fuori da questa lista, oppure imposta un limite di work in progress più alto.", - "admin-panel": "Pannello dell'Amministratore", - "settings": "Impostazioni", - "people": "Persone", - "registration": "Registrazione", - "disable-self-registration": "Disabilita Auto-registrazione", - "invite": "Invita", - "invite-people": "Invita persone", - "to-boards": "Alla(e) bacheca", - "email-addresses": "Indirizzi email", - "smtp-host-description": "L'indirizzo del server SMTP che gestisce le tue email.", - "smtp-port-description": "La porta che il tuo server SMTP utilizza per le email in uscita.", - "smtp-tls-description": "Abilita supporto TLS per server SMTP", - "smtp-host": "SMTP Host", - "smtp-port": "Porta SMTP", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "Supporto TLS", - "send-from": "Da", - "send-smtp-test": "Invia un'email di test a te stesso", - "invitation-code": "Codice d'invito", - "email-invite-register-subject": "__inviter__ ti ha inviato un invito", - "email-invite-register-text": "Gentile __user__,\n\n__inviter__ ti ha invitato a partecipare a questa bacheca kanban per collaborare.\n\nPer favore segui il collegamento qui sotto:\n__url__\n\nIl tuo codice di invito è: __icode__\n\nGrazie.", - "email-smtp-test-subject": "E-Mail di prova SMTP", - "email-smtp-test-text": "Hai inviato un'email con successo", - "error-invitation-code-not-exist": "Il codice d'invito non esiste", - "error-notAuthorized": "Non sei autorizzato ad accedere a questa pagina.", - "webhook-title": "Nome Webhook", - "webhook-token": "Token (facoltativo per l'autenticazione)", - "outgoing-webhooks": "Server esterni", - "bidirectional-webhooks": "Webhook a due vie", - "outgoingWebhooksPopup-title": "Server esterni", - "boardCardTitlePopup-title": "Filtro per Titolo Scheda", - "disable-webhook": "Disattiva questo Webhook", - "global-webhook": "Webhook globali", - "new-outgoing-webhook": "Nuovo webhook in uscita", - "no-name": "(Sconosciuto)", - "Node_version": "Versione di Node", - "Meteor_version": "Versione Meteor", - "MongoDB_version": "Versione MondoDB", - "MongoDB_storage_engine": "Versione motore dati MongoDB", - "MongoDB_Oplog_enabled": "MongoDB Oplog abilitato", - "OS_Arch": "Architettura del sistema operativo", - "OS_Cpus": "Conteggio della CPU del sistema operativo", - "OS_Freemem": "Memoria libera del sistema operativo", - "OS_Loadavg": "Carico medio del sistema operativo", - "OS_Platform": "Piattaforma del sistema operativo", - "OS_Release": "Versione di rilascio del sistema operativo", - "OS_Totalmem": "Memoria totale del sistema operativo", - "OS_Type": "Tipo di sistema operativo", - "OS_Uptime": "Tempo di attività del sistema operativo.", - "days": "giorni", - "hours": "ore", - "minutes": "minuti", - "seconds": "secondi", - "show-field-on-card": "Visualizza questo campo sulla scheda", - "automatically-field-on-card": "Crea automaticamente i campi per tutte le schede", - "showLabel-field-on-card": "Mostra l'etichetta di campo su minischeda", - "yes": "Sì", - "no": "No", - "accounts": "Profili", - "accounts-allowEmailChange": "Permetti modifica dell'email", - "accounts-allowUserNameChange": "Consenti la modifica del nome utente", - "createdAt": "creato alle", - "verified": "Verificato", - "active": "Attivo", - "card-received": "Ricevuta", - "card-received-on": "Ricevuta il", - "card-end": "Fine", - "card-end-on": "Termina il", - "editCardReceivedDatePopup-title": "Cambia data ricezione", - "editCardEndDatePopup-title": "Cambia data finale", - "setCardColorPopup-title": "Imposta il colore", - "setCardActionsColorPopup-title": "Scegli un colore", - "setSwimlaneColorPopup-title": "Scegli un colore", - "setListColorPopup-title": "Scegli un colore", - "assigned-by": "Assegnato da", - "requested-by": "Richiesto da", - "board-delete-notice": "L'eliminazione è permanente. Tutte le azioni, liste e schede associate a questa bacheca andranno perse.", - "delete-board-confirm-popup": "Tutte le liste, schede, etichette e azioni saranno rimosse e non sarai più in grado di recuperare il contenuto della bacheca. L'azione non è annullabile.", - "boardDeletePopup-title": "Eliminare la bacheca?", - "delete-board": "Elimina bacheca", - "default-subtasks-board": "Sottocompiti per la bacheca __board__", - "default": "Predefinito", - "queue": "Coda", - "subtask-settings": "Impostazioni sotto-compiti", - "card-settings": "Impostazioni della scheda", - "boardSubtaskSettingsPopup-title": "Impostazioni sotto-compiti della bacheca", - "boardCardSettingsPopup-title": "Impostazioni della scheda", - "deposit-subtasks-board": "Deposita i sotto compiti in questa bacheca", - "deposit-subtasks-list": "Lista di destinaizoni per questi sotto-compiti", - "show-parent-in-minicard": "Mostra genirotri nelle mini schede:", - "prefix-with-full-path": "Prefisso con percorso completo", - "prefix-with-parent": "Prefisso con genitore", - "subtext-with-full-path": "Sottotesto con percorso completo", - "subtext-with-parent": "Sotto-testo con genitore", - "change-card-parent": "Cambia la scheda genitore", - "parent-card": "Scheda genitore", - "source-board": "Bacheca d'origine", - "no-parent": "Non mostrare i genitori", - "activity-added-label": "L' etichetta '%s' è stata aggiunta a %s", - "activity-removed-label": "L'etichetta '%s' è stata rimossa da %s", - "activity-delete-attach": "Rimosso un allegato da %s", - "activity-added-label-card": "aggiunta etichetta '%s'", - "activity-removed-label-card": "L' etichetta '%s' è stata rimossa.", - "activity-delete-attach-card": "Cancella un allegato", - "activity-set-customfield": "imposta campo personalizzato '%s' a '%s' in %s", - "activity-unset-customfield": "campo personalizzato non impostato '%s' in %s", - "r-rule": "Ruolo", - "r-add-trigger": "Aggiungi trigger", - "r-add-action": "Aggiungi azione", - "r-board-rules": "Regole della bacheca", - "r-add-rule": "Aggiungi regola", - "r-view-rule": "Visualizza regola", - "r-delete-rule": "Cancella regola", - "r-new-rule-name": "Titolo nuova regola", - "r-no-rules": "Nessuna regola", - "r-when-a-card": "Quando una scheda", - "r-is": "è", - "r-is-moved": "viene spostata", - "r-added-to": "Aggiunto/a a", - "r-removed-from": "Rimosso da", - "r-the-board": "La bacheca", - "r-list": "lista", - "set-filter": "Imposta un filtro", - "r-moved-to": "Spostato/a a", - "r-moved-from": "Spostato/a da", - "r-archived": "Spostato/a nell'archivio", - "r-unarchived": "Ripristinato/a dall'archivio", - "r-a-card": "una scheda", - "r-when-a-label-is": "Quando un'etichetta viene", - "r-when-the-label": "Quando l'etichetta viene", - "r-list-name": "Nome dell'elenco", - "r-when-a-member": "Quando un membro viene", - "r-when-the-member": "Quando un membro viene", - "r-name": "nome", - "r-when-a-attach": "Quando un allegato", - "r-when-a-checklist": "Quando una checklist è", - "r-when-the-checklist": "Quando la checklist", - "r-completed": "Completato/a", - "r-made-incomplete": "Rendi incompleto", - "r-when-a-item": "Quando un elemento della checklist è", - "r-when-the-item": "Quando un elemento della checklist", - "r-checked": "Selezionato", - "r-unchecked": "Deselezionato", - "r-move-card-to": "Sposta scheda a", - "r-top-of": "Al di sopra di", - "r-bottom-of": "Al di sotto di", - "r-its-list": "il suo elenco", - "r-archive": "Sposta nell'Archivio", - "r-unarchive": "Ripristina dall'archivio", - "r-card": "scheda", - "r-add": "Aggiungere", - "r-remove": "Rimuovi", - "r-label": "etichetta", - "r-member": "membro", - "r-remove-all": "Rimuovi tutti i membri dalla scheda", - "r-set-color": "Imposta il colore a", - "r-checklist": "checklist", - "r-check-all": "Spunta tutti", - "r-uncheck-all": "Togli la spunta a tutti", - "r-items-check": "Elementi della checklist", - "r-check": "Spunta", - "r-uncheck": "Togli la spunta", - "r-item": "elemento", - "r-of-checklist": "della lista di cose da fare", - "r-send-email": "Invia un e-mail", - "r-to": "a", - "r-subject": "soggetto", - "r-rule-details": "Dettagli della regola", - "r-d-move-to-top-gen": "Sposta la scheda al di sopra del suo elenco", - "r-d-move-to-top-spec": "Sposta la scheda la di sopra dell'elenco", - "r-d-move-to-bottom-gen": "Sposta la scheda in fondo alla sua lista", - "r-d-move-to-bottom-spec": "Muovi la scheda in fondo alla lista", - "r-d-send-email": "Spedisci email", - "r-d-send-email-to": "a", - "r-d-send-email-subject": "soggetto", - "r-d-send-email-message": "Messaggio", - "r-d-archive": "Sposta la scheda nell'archivio", - "r-d-unarchive": "Ripristina la scheda dall'archivio", - "r-d-add-label": "Aggiungi etichetta", - "r-d-remove-label": "Rimuovi Etichetta", - "r-create-card": "Crea una nuova scheda", - "r-in-list": "in elenco", - "r-in-swimlane": "nel diagramma swimlane", - "r-d-add-member": "Aggiungi membro", - "r-d-remove-member": "Rimuovi membro", - "r-d-remove-all-member": "Rimouvi tutti i membri", - "r-d-check-all": "Seleziona tutti gli item di una lista", - "r-d-uncheck-all": "Deseleziona tutti gli items di una lista", - "r-d-check-one": "Seleziona", - "r-d-uncheck-one": "Deselezionalo", - "r-d-check-of-list": "della lista di cose da fare", - "r-d-add-checklist": "Aggiungi lista di cose da fare", - "r-d-remove-checklist": "Rimuovi check list", - "r-by": "da", - "r-add-checklist": "Aggiungi lista di cose da fare", - "r-with-items": "con elementi", - "r-items-list": "elemento1,elemento2,elemento3", - "r-add-swimlane": "Aggiungi un diagramma swimlane", - "r-swimlane-name": "Nome diagramma swimlane", - "r-board-note": "Nota: Lascia un campo vuoto per abbinare ogni possibile valore", - "r-checklist-note": "Nota: Gli elementi della checklist devono essere scritti come valori separati dalla virgola", - "r-when-a-card-is-moved": "Quando una scheda viene spostata su un'altra lista", - "r-set": "Imposta", - "r-update": "Aggiorna", - "r-datefield": "campo data", - "r-df-start-at": "inizio", - "r-df-due-at": "scadenza", - "r-df-end-at": "fine", - "r-df-received-at": "ricevuta", - "r-to-current-datetime": "a data/ora corrente", - "r-remove-value-from": "Rimuovi valore da", - "ldap": "LDAP", - "oauth2": "Oauth2", - "cas": "CAS", - "authentication-method": "Metodo di Autenticazione", - "authentication-type": "Tipo Autenticazione", - "custom-product-name": "Nome prodotto personalizzato", - "layout": "Layout", - "hide-logo": "Nascondi il logo", - "add-custom-html-after-body-start": "Aggiungi codice HTML personalizzato dopo <body> inzio", - "add-custom-html-before-body-end": "Aggiunti il codice HTML prima di </body> fine", - "error-undefined": "Qualcosa è andato storto", - "error-ldap-login": "C'è stato un errore mentre provavi ad effettuare il login", - "display-authentication-method": "Mostra il metodo di autenticazione", - "default-authentication-method": "Metodo di autenticazione predefinito", - "duplicate-board": "Duplica bacheca", - "people-number": "Il numero di persone è:", - "swimlaneDeletePopup-title": "Cancella diagramma swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Cancella tutto", - "loading": "Loading, please wait.", - "previous_as": "l'ultima volta è stata", - "act-a-dueAt": "Scadenza modificata in __timeValue__\nData precedente: __timeOldValue__", - "act-a-endAt": "orario finale modificato in __timeValue__ (precedentemente: __timeOldValue__)", - "act-a-startAt": "orario iniziale modificato in __timeValue__ (precedentemente: __timeOldValue__)", - "act-a-receivedAt": "orario di ricezione modificato in __timeValue__ (precedentemente: __timeOldValue__)", - "a-dueAt": "scadenza modificata in", - "a-endAt": "orario finale modificato in", - "a-startAt": "orario iniziale modificato in", - "a-receivedAt": "orario di ricezione modificato in", - "almostdue": "la data di scadenza attuale %s si sta avvicinando", - "pastdue": "la data di scadenza attuale %s è scaduta", - "duenow": "la data di scadenza attuale %s è oggi", - "act-newDue": "__list__/__card__ ha un 1° sollecito [__board__]", - "act-withDue": "sollecito relativo a __list__/__card__ [__board__]", - "act-almostdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è vicina", - "act-pastdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è già passata", - "act-duenow": "sollecito inviato: la scadenza (__timeValue__) di __card__ è adesso", - "act-atUserComment": "Sei stato menzionato in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Sei sicuro di voler cancellare questo profilo? Non sarà possibile ripristinarlo.", - "accounts-allowUserDelete": "Permetti agli utenti di cancellare il loro profilo", - "hide-minicard-label-text": "Nascondi etichetta minicard", - "show-desktop-drag-handles": "Mostra maniglie di trascinamento del desktop", - "assignee": "Assegnatario", - "cardAssigneesPopup-title": "Assegnatario", - "addmore-detail": "Aggiungi una descrizione più dettagliata", - "show-on-card": "Mostra sulla scheda", - "new": "Nuovo", - "editUserPopup-title": "Modifica utente", - "newUserPopup-title": "Nuovo utente", - "notifications": "Notifiche", - "view-all": "Mostra Tutto", - "filter-by-unread": "Filtra per non letto", - "mark-all-as-read": "Segna come letto", - "allow-rename": "Consenti Rinomina", - "allowRenamePopup-title": "Consenti Rinomina" -} + "accept": "Accetta", + "act-activity-notify": "Notifica attività", + "act-addAttachment": "Aggiunto allegato __attachment__ alla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-deleteAttachment": "Eliminato allegato __attachment__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-addSubtask": "Aggiunto sottotask __subtask__ alla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-addLabel": "Aggiunta etichetta __label__ alla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-addedLabel": "Aggiunta etichetta __label__ alla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-removeLabel": "Rimossa etichetta __label__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-removedLabel": "Rimossa etichetta __label__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-addChecklist": "aggiunta lista di controllo __label__ alla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-addChecklistItem": "aggiunto elemento __checklistItem__ alla lista di controllo __checklist__ della scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-removeChecklist": "rimossa lista di controllo __checklist__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-removeChecklistItem": "rimosso elemento __checklistItem__ dalla lista di controllo __checkList__ della scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "act-checkedItem": "attivato __checklistItem__ nella lista di controllo __checklist__ della scheda __card__ della lista __list__ della swimlane __swimlane__ nella bacheca __board__", + "act-uncheckedItem": "disattivato __checklistItem__ della lista di controllo __checklist__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__ nella bacheca __board__", + "act-completeChecklist": "completata lista di controllo __checklist__ nella scheda __card__ della lista __list__ della swimlane __swimlane__ nella bacheca __board__", + "act-uncompleteChecklist": "lista di controllo __checklist__ incompleta nella scheda __card__ della lista __list__ in swimlane __swimlane__ della bacheca __board__", + "act-addComment": "commento sulla scheda __card__: __comment__ nella lista __list__ della swimlane __swimlane__ della bacheca __board__", + "act-editComment": "commento modificato sulla scheda __card__: __comment__ nella lista __list__ nella swim lane __swimlane__ nella bacheca __board__", + "act-deleteComment": "commento eliminato sulla scheda __card__: __comment__ nella lista __list__ nella swim lane __swimlane__ nella bacheca __board__", + "act-createBoard": "bacheca __board__ creata", + "act-createSwimlane": "creata swimlane __swimlane__ alla bacheca __board__", + "act-createCard": "scheda __card__ creata nella lista __list__ della swimlane __swimlane__ della bacheca __board__", + "act-createCustomField": "creato campo personalizzato __customField__ nella bacheca __board", + "act-deleteCustomField": "campo personalizzato eliminato __customField__ nella bacheca __board", + "act-setCustomField": "campo personalizzato modificato __customField__: __customFieldValue__ sulla scheda __card__ sulla lista __list__ sulla swimlane __swimlane__ sulla bacheca __board__", + "act-createList": "aggiunta lista __list__ alla bacheca __board__", + "act-addBoardMember": "aggiunto membro __member__ alla bacheca __board__", + "act-archivedBoard": "Bacheca __board__ archiviata", + "act-archivedCard": "Scheda __card__ della lista __list__ della swimlane __swimlane__ della bacheca __board__ archiviata", + "act-archivedList": "Lista __list__ della swimlane __swimlane__ della bacheca __board__ archiviata", + "act-archivedSwimlane": "Corsia __swimlane__ della bacheca __board__ archiviata", + "act-importBoard": "Bacheca __board__ importata", + "act-importCard": "scheda importata __card__ nella lista __list__ della swimlane __swimlane__ della bacheca __board__", + "act-importList": "lista __list__ importata nella swimlane __swimlane__ della bacheca __board__", + "act-joinMember": "aggiunto membro __member__ alla scheda __card__ della list __list__ nella swimlane __swimlane__ della bacheca __board__", + "act-moveCard": "spostata scheda __card__ della bacheca __board__ dalla lista __oldList__ della swimlane __oldSwimlane__ alla lista __list__ della swimlane __swimlane__", + "act-moveCardToOtherBoard": "postata scheda __card__ dalla lista __oldList__ della swimlane __oldSwimlane__ della bacheca __oldBoard__ alla lista __list__ nella swimlane __swimlane__ della bacheca __board__", + "act-removeBoardMember": "rimosso membro __member__ dalla bacheca __board__", + "act-restoredCard": "scheda ripristinata __card__ della lista __list__ nella swimlane __swimlane__ della bacheca __board__", + "act-unjoinMember": "rimosso membro __member__ dalla scheda __card__ della lista __list__ della swimlane __swimlane__ nella bacheca __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Azioni", + "activities": "Attività", + "activity": "Attività", + "activity-added": "ha aggiunto %s a %s", + "activity-archived": "%s spostato nell'archivio", + "activity-attached": "allegato %s a %s", + "activity-created": "creato %s", + "activity-customfield-created": "%s creato come campo personalizzato", + "activity-excluded": "escluso %s da %s", + "activity-imported": "importato %s in %s da %s", + "activity-imported-board": "importato %s da %s", + "activity-joined": "si è unito a %s", + "activity-moved": "ha spostato %s da %s a %s", + "activity-on": "su %s", + "activity-removed": "rimosso %s da %s", + "activity-sent": "inviato %s a %s", + "activity-unjoined": "ha abbandonato %s", + "activity-subtask-added": "aggiunto il sottocompito a 1%s", + "activity-checked-item": "selezionata %s nella checklist %s di %s", + "activity-unchecked-item": "disattivato %s nella checklist %s di %s", + "activity-checklist-added": "ha aggiunto una checklist a %s", + "activity-checklist-removed": "È stata rimossa una checklist da%s", + "activity-checklist-completed": "%s di %s checklists completate", + "activity-checklist-uncompleted": "La checklist non è stata completata", + "activity-checklist-item-added": "Aggiunto l'elemento checklist a '%s' in %s", + "activity-checklist-item-removed": "è stato rimosso un elemento della checklist da '%s' in %s", + "add": "Aggiungi", + "activity-checked-item-card": "%s è stato selezionato nella checklist %s", + "activity-unchecked-item-card": "%s è stato deselezionato nella checklist %s", + "activity-checklist-completed-card": "checklist __checklist__ completata nella scheda __card__ della lista __list__ della swimlane __swimlane__  nella bacheca __board__", + "activity-checklist-uncompleted-card": "La checklist %s non è completa", + "activity-editComment": "commento modificato %s", + "activity-deleteComment": "commento eliminato %s", + "activity-receivedDate": "ha modificato la data di ricevuta a %s di %s", + "activity-startDate": "ha modificato la data di inizio a %s di %s", + "activity-dueDate": "ha modificato la data di scadenza a %s di %s", + "activity-endDate": "ha modificato la data di fine a %s di %s ", + "add-attachment": "Aggiungi allegato", + "add-board": "Aggiungi bacheca", + "add-template": "Add Template", + "add-card": "Aggiungi scheda", + "add-card-to-top-of-list": "Aggiungi Scheda in cima alla Lista", + "add-card-to-bottom-of-list": "Aggiungi Scheda in fondo alla Lista", + "add-swimlane": "Aggiungi swimlane", + "add-subtask": "Aggiungi sotto-compito", + "add-checklist": "Aggiungi Checklist", + "add-checklist-item": "Aggiungi un elemento alla checklist", + "add-cover": "Aggiungi copertina", + "add-label": "Aggiungi etichetta", + "add-list": "Aggiungi lista", + "add-members": "Aggiungi membri", + "added": "Aggiunto/a", + "addMemberPopup-title": "Membri", + "admin": "Amministratore", + "admin-desc": "Può vedere e modificare schede, rimuovere membri e modificare le impostazioni della bacheca.", + "admin-announcement": "Annunci", + "admin-announcement-active": "Attiva annunci di sistema", + "admin-announcement-title": "Annunci dell'Amministratore", + "all-boards": "Tutte le bacheche", + "and-n-other-card": "E __count__ altra scheda", + "and-n-other-card_plural": "E __count__ altre schede", + "apply": "Applica", + "app-is-offline": "Caricamento, attendere prego. Aggiornare la pagina porterà ad una perdita dei dati. Se il caricamento non dovesse funzionare, per favore controlla che il server non sia stato fermato.", + "archive": "Sposta nell'archivio", + "archive-all": "Sposta tutto nell'archivio", + "archive-board": "Sposta la bacheca nell'archivio", + "archive-card": "Sposta scheda nell'archivio", + "archive-list": "Sposta lista nell'archivio", + "archive-swimlane": "Sposta swimlane nell'archivio", + "archive-selection": "Sposta elementi selezionati nell'archivio", + "archiveBoardPopup-title": "Spostare bacheca nell'archivio?", + "archived-items": "Archivio", + "archived-boards": "Bacheche nell'archivio", + "restore-board": "Ripristina bacheca", + "no-archived-boards": "Nessuna bacheca presente nell'archivio.", + "archives": "Archivio", + "template": "Template", + "templates": "Template", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assegna a un membro", + "attached": "allegato", + "attachment": "Allegato", + "attachment-delete-pop": "L'eliminazione di un allegato è permanente. Non è possibile tornare indietro.", + "attachmentDeletePopup-title": "Eliminare l'allegato?", + "attachments": "Allegati", + "auto-watch": "Segui automaticamente le bacheche quando vengono create.", + "avatar-too-big": "L'avatar è troppo grande (520 KB massimo)", + "back": "Indietro", + "board-change-color": "Cambia colore", + "board-nb-stars": "%s stelle", + "board-not-found": "Bacheca non trovata", + "board-private-info": "Questa bacheca sarà <strong>privata</strong>.", + "board-public-info": "Questa bacheca sarà <strong>pubblica</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Cambia sfondo della bacheca", + "boardChangeTitlePopup-title": "Rinomina bacheca", + "boardChangeVisibilityPopup-title": "Cambia visibilità", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Impostazioni bacheca", + "boardChangeViewPopup-title": "Vista bacheca", + "boards": "Bacheche", + "board-view": "Vista bacheca", + "board-view-cal": "Calendario", + "board-view-swimlanes": "Swimlane", + "board-view-collapse": "Comprimi", + "board-view-gantt": "Gantt", + "board-view-lists": "Liste", + "bucket-example": "Come \"una lista di cose da fare\" per esempio", + "cancel": "Cancella", + "card-archived": "Questa scheda è stata spostata nell'archivio", + "board-archived": "Questa bacheca è stata spostata nell'archivio", + "card-comments-title": "Questa scheda ha %s commenti.", + "card-delete-notice": "L'eliminazione è permanente. Tutte le azioni associate a questa scheda andranno perse.", + "card-delete-pop": "Tutte le azioni saranno rimosse dal flusso attività e non sarai in grado di riaprire la scheda. Non potrai tornare indietro.", + "card-delete-suggest-archive": "Puoi spostare una scheda nell'archivio per rimuoverla dalla bacheca e mantenere la sua attività.", + "card-due": "Scadenza", + "card-due-on": "Scade il", + "card-spent": "Tempo trascorso", + "card-edit-attachments": "Modifica allegati", + "card-edit-custom-fields": "Modifica campo personalizzato", + "card-edit-labels": "Modifica etichette", + "card-edit-members": "Modifica membri", + "card-labels-title": "Cambia le etichette per questa scheda.", + "card-members-title": "Aggiungi o rimuovi membri della bacheca da questa scheda", + "card-start": "Inizio", + "card-start-on": "Inizia il", + "cardAttachmentsPopup-title": "Allega da", + "cardCustomField-datePopup-title": "Cambia data", + "cardCustomFieldsPopup-title": "Modifica campo personalizzato", + "cardStartVotingPopup-title": "Avvia una votazione", + "positiveVoteMembersPopup-title": "Favorevoli", + "negativeVoteMembersPopup-title": "Contrari", + "card-edit-voting": "Modifica la votazione", + "editVoteEndDatePopup-title": "Cambia data di termine voto", + "allowNonBoardMembers": "Consenti tutti gli utenti registrati", + "vote-question": "Domanda di votazione", + "vote-public": "Mostrare chi ha votato cosa", + "vote-for-it": "a favore", + "vote-against": "contro", + "deleteVotePopup-title": "Eliminare il voto?", + "vote-delete-pop": "L'eliminazione è permanente. Tutte le azioni associate a questa votazione andranno perse.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Fine", + "poker-result-votes": "Voti", + "poker-result-who": "Chi", + "poker-replay": "Replay", + "set-estimation": "Imposta la stima", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Eliminare scheda?", + "cardDetailsActionsPopup-title": "Azioni scheda", + "cardLabelsPopup-title": "Etichette", + "cardMembersPopup-title": "Membri", + "cardMorePopup-title": "Altro", + "cardTemplatePopup-title": "Crea un template", + "cards": "Schede", + "cards-count": "Schede", + "cards-count-one": "Scheda", + "casSignIn": "Entra con CAS", + "cardType-card": "Scheda", + "cardType-linkedCard": "Scheda collegata", + "cardType-linkedBoard": "Bacheca collegata", + "change": "Modifica", + "change-avatar": "Cambia avatar", + "change-password": "Modifica password", + "change-permissions": "Modifica permessi", + "change-settings": "Modifica impostazioni", + "changeAvatarPopup-title": "Cambia avatar", + "changeLanguagePopup-title": "Cambia lingua", + "changePasswordPopup-title": "Modifica password", + "changePermissionsPopup-title": "Modifica permessi", + "changeSettingsPopup-title": "Modifica impostazioni", + "subtasks": "Sotto-compiti", + "checklists": "Checklist", + "click-to-star": "Clicca per stellare questa bacheca", + "click-to-unstar": "Clicca per togliere la stella da questa bacheca.", + "clipboard": "Appunti o trascinamento", + "close": "Chiudi", + "close-board": "Chiudi bacheca", + "close-board-pop": "Potrai ripristinare la bacheca cliccando sul tasto \"Archivio\" presente nell'intestazione della home.", + "close-card": "Chiudi Scheda", + "color-black": "nero", + "color-blue": "blu", + "color-crimson": "Rosso cremisi", + "color-darkgreen": "verde scuro", + "color-gold": "oro", + "color-gray": "grigio", + "color-green": "verde", + "color-indigo": "Indaco", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "Mistyrose", + "color-navy": "Navy", + "color-orange": "arancione", + "color-paleturquoise": "Turchese chiaro", + "color-peachpuff": "Pesca", + "color-pink": "rosa", + "color-plum": "Prugna", + "color-purple": "viola", + "color-red": "rosso", + "color-saddlebrown": "Saddlebrown", + "color-silver": "argento", + "color-sky": "azzurro", + "color-slateblue": "Ardesia", + "color-white": "bianco", + "color-yellow": "giallo", + "unset-color": "Non impostato", + "comment": "Commenta", + "comment-placeholder": "Scrivi un commento...", + "comment-only": "Solo commenti", + "comment-only-desc": "Puoi commentare solo le schede.", + "no-comments": "Non ci sono commenti.", + "no-comments-desc": "Impossibile visualizzare commenti o attività.", + "worker": "Lavoratore", + "worker-desc": "Può solo spostare schede, assegnarsi una scheda e commentare.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Sei sicuro di voler eliminare il sotto-compito?", + "confirm-checklist-delete-dialog": "Sei sicuro di voler eliminare la checklist?", + "copy-card-link-to-clipboard": "Copia link della scheda negli appunti", + "linkCardPopup-title": "Collega scheda", + "searchElementPopup-title": "Cerca", + "copyCardPopup-title": "Copia scheda", + "copyChecklistToManyCardsPopup-title": "Copia template checklist su più schede", + "copyChecklistToManyCardsPopup-instructions": "Titolo e la descrizione della scheda di destinazione in questo formato JSON", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titolo prima scheda\", \"description\":\"Descrizione prima scheda\"}, {\"title\":\"Titolo seconda scheda\",\"description\":\"Descrizione seconda scheda\"},{\"title\":\"Titolo ultima scheda\",\"description\":\"Descrizione ultima scheda\"} ]", + "create": "Crea", + "createBoardPopup-title": "Crea bacheca", + "chooseBoardSourcePopup-title": "Importa bacheca", + "createLabelPopup-title": "Crea etichetta", + "createCustomField": "Crea campo", + "createCustomFieldPopup-title": "Crea campo", + "current": "corrente", + "custom-field-delete-pop": "Non potrai tornare indietro. Questa azione rimuoverà questo campo personalizzato da tutte le schede ed eliminerà ogni sua traccia.", + "custom-field-checkbox": "Casella di scelta", + "custom-field-currency": "Valuta", + "custom-field-currency-option": "Codice Valuta", + "custom-field-date": "Data", + "custom-field-dropdown": "Lista a discesa", + "custom-field-dropdown-none": "(niente)", + "custom-field-dropdown-options": "Opzioni lista", + "custom-field-dropdown-options-placeholder": "Premi invio per aggiungere altre opzioni", + "custom-field-dropdown-unknown": "(sconosciuto)", + "custom-field-number": "Numero", + "custom-field-text": "Testo", + "custom-fields": "Campi personalizzati", + "date": "Data", + "decline": "Rifiuta", + "default-avatar": "Avatar predefinito", + "delete": "Elimina", + "deleteCustomFieldPopup-title": "Elimina il campo personalizzato?", + "deleteLabelPopup-title": "Eliminare etichetta?", + "description": "Descrizione", + "disambiguateMultiLabelPopup-title": "Disambiguare l'azione Etichetta", + "disambiguateMultiMemberPopup-title": "Disambiguare l'azione Membro", + "discard": "Scarta", + "done": "Fatto", + "download": "Scarica", + "edit": "Modifica", + "edit-avatar": "Cambia avatar", + "edit-profile": "Modifica profilo", + "edit-wip-limit": "Modifica limite di work in progress", + "soft-wip-limit": "Limite Work in progress soft", + "editCardStartDatePopup-title": "Cambia data di inizio", + "editCardDueDatePopup-title": "Cambia data di scadenza", + "editCustomFieldPopup-title": "Modifica campo", + "editCardSpentTimePopup-title": "Cambia tempo trascorso", + "editLabelPopup-title": "Modifica etichetta", + "editNotificationPopup-title": "Modifica notifiche", + "editProfilePopup-title": "Modifica profilo", + "email": "Email", + "email-enrollAccount-subject": "Creato un account per te su __siteName__", + "email-enrollAccount-text": "Ciao __user__,\n\nPer iniziare ad utilizzare il servizio, clicca sul link seguente:\n\n__url__\n\nGrazie.", + "email-fail": "Invio email fallito", + "email-fail-text": "Errore nel tentativo di invio email", + "email-invalid": "Email non valida", + "email-invite": "Invita via email", + "email-invite-subject": "__inviter__ ti ha inviato un invito", + "email-invite-text": "Caro __user__,\n\n__inviter__ ti ha invitato ad unirti alla bacheca \"__board__\" per collaborare.\n\nPer favore clicca sul link seguente:\n\n__url__\n\nGrazie.", + "email-resetPassword-subject": "Ripristina la tua password su on __siteName__", + "email-resetPassword-text": "Ciao __user__,\n\nPer ripristinare la tua password, clicca sul link seguente:\n\n__url__\n\nGrazie.", + "email-sent": "Email inviata", + "email-verifyEmail-subject": "Verifica il tuo indirizzo email su __siteName__", + "email-verifyEmail-text": "Ciao __user__,\n\nPer verificare il tuo account email, clicca sul link seguente:\n\n__url__\n\nGrazie.", + "enable-wip-limit": "Attiva limite Work In Progress", + "error-board-doesNotExist": "Questa bacheca non esiste", + "error-board-notAdmin": "Devi essere admin di questa bacheca per poterlo fare", + "error-board-notAMember": "Devi essere un membro di questa bacheca per poterlo fare", + "error-json-malformed": "Il testo non è in un formato JSON valido", + "error-json-schema": "Il tuo file JSON non contiene le giuste informazioni nel formato corretto", + "error-csv-schema": "Il tuo file CSV (Comma Separated Values) / TSV (Tab Separated Values) non contiene le giuste informazioni nel formato corretto ", + "error-list-doesNotExist": "Questa lista non esiste", + "error-user-doesNotExist": "Questo utente non esiste", + "error-user-notAllowSelf": "Non puoi invitare te stesso", + "error-user-notCreated": "L'utente non è stato creato", + "error-username-taken": "Questo username è già utilizzato", + "error-orgname-taken": "Il nome di questa organizzazione è già stato scelto", + "error-teamname-taken": "Il nome di questo team è già stato preso", + "error-email-taken": "L'email è già stata presa", + "export-board": "Esporta bacheca", + "export-board-json": "Esporta bacheca in JSON", + "export-board-csv": "Esporta bacheca in CSV", + "export-board-tsv": "Esporta bacheca in TSV", + "export-board-excel": "Esporta bacheca in Excel", + "user-can-not-export-excel": "L'utente non può effettuare l'esportazione in Excel", + "export-board-html": "Esporta bacheca in HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Esporta bacheca", + "exportCardPopup-title": "Export card", + "sort": "Ordina", + "sort-desc": "Clicca per ordinare la lista", + "list-sort-by": "Ordina lista per:", + "list-label-modifiedAt": "Orario ultimo accesso", + "list-label-title": "Nome della lista", + "list-label-sort": "Il tuo ordine manuale", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filtra", + "filter-cards": "Filtra schede o liste", + "filter-dates-label": "Filtra per data", + "filter-no-due-date": "Senza data scadenza", + "filter-overdue": "Scaduta", + "filter-due-today": "Scade oggi", + "filter-due-this-week": "Scade questa settimana", + "filter-due-tomorrow": "Scade domani", + "list-filter-label": "Filtra lista per titolo", + "filter-clear": "Pulisci filtri", + "filter-labels-label": "Filtra secondo l'etichetta", + "filter-no-label": "Nessuna etichetta", + "filter-member-label": "Filtra secondo il membro", + "filter-no-member": "Nessun membro", + "filter-assignee-label": "Filtra secondo l'assegnatario", + "filter-no-assignee": "Nessun assegnatario", + "filter-custom-fields-label": "Filtra secondo campi personalizzati", + "filter-no-custom-fields": "Nessun campo personalizzato", + "filter-show-archive": "Mostra liste archiviate", + "filter-hide-empty": "Nascondi liste vuote", + "filter-on": "Il filtro è attivo", + "filter-on-desc": "Stai filtrando le schede su questa bacheca. Clicca qui per modificare il filtro,", + "filter-to-selection": "Filtra selezione", + "other-filters-label": "Altri flitri", + "advanced-filter-label": "Filtro avanzato", + "advanced-filter-description": "Il filtro avanzato permette di scrivere una stringa contenente i seguenti operatori: == != <= >= && || ( ) Uno spazio è usato come separatore tra gli operatori. Si può filtrare per tutti i campi personalizzati, scrivendo i loro nomi e valori. Per esempio: Campo1 == Valore1. Nota: Se i campi o i valori contengono spazi, è necessario incapsularli all'interno di apici singoli. Per esempio: 'Campo 1' == 'Valore 1'. Per i singoli caratteri di controllo (' V) che devono essere ignorati, si può usare \\. Per esempio: C1 == Campo1 == L'\\ho. Si possono anche combinare condizioni multiple. Per esempio: C1 == V1 || C1 ==V2. Di norma tutti gli operatori vengono letti da sinistra a destra. Si può cambiare l'ordine posizionando delle parentesi. Per esempio: C1 == V1 && ( C2 == V2 || C2 == V3) . Inoltre è possibile cercare nei campi di testo usando le espressioni regolari (n.d.t. regex): F1 ==/Tes.*/i", + "fullname": "Nome completo", + "header-logo-title": "Torna alla pagina delle tue bacheche.", + "hide-system-messages": "Nascondi i messaggi di sistema", + "headerBarCreateBoardPopup-title": "Crea bacheca", + "home": "Home", + "import": "Importa", + "impersonate-user": "Impersona utente", + "link": "Collegamento", + "import-board": "Importa bacheca", + "import-board-c": "Importa bacheca", + "import-board-title-trello": "Importa una bacheca da Trello", + "import-board-title-wekan": "Importa bacheca da esportazione precedente", + "import-board-title-csv": "Importa bacheca da CSV/TSV", + "from-trello": "Da Trello", + "from-wekan": "Da esportazione precedente", + "from-csv": "Da CSV/TSV", + "import-board-instruction-trello": "Nella tua bacheca Trello vai a 'Menu', poi 'Altro', 'Stampa ed esporta', 'Esporta JSON', e copia il testo che compare.", + "import-board-instruction-csv": "Incolla in Comma Separated Values (CSV) / Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "Nella tua bacheca vai su \"Menu\", poi \"Esporta bacheca\" e copia il testo nel file scaricato.", + "import-board-instruction-about-errors": "Se hai degli errori quando importi una bacheca, qualche volta l'importazione funziona comunque, e la bacheca si trova nella pagina \"Tutte le bacheche\"", + "import-json-placeholder": "Incolla un JSON valido qui", + "import-csv-placeholder": "Incolla un CSV/TSV valido qui", + "import-map-members": "Mappatura dei membri", + "import-members-map": "La bacheca che hai importato contiene alcuni membri. Per favore scegli i membri che vuoi importare tra i tuoi utenti", + "import-members-map-note": "Nota: I membri non mappati verranno assegnati all'utente corrente.", + "import-show-user-mapping": "Rivedi la mappatura dei membri", + "import-user-select": "Scegli l'utente che vuoi venga utilizzato come questo membro", + "importMapMembersAddPopup-title": "Scegli membro", + "info": "Versione", + "initials": "Iniziali", + "invalid-date": "Data non valida", + "invalid-time": "Tempo non valido", + "invalid-user": "Utente non valido", + "joined": "si è unito a", + "just-invited": "Sei stato appena invitato a questa bacheca", + "keyboard-shortcuts": "Scorciatoie da tastiera", + "label-create": "Crea etichetta", + "label-default": "%s etichetta (default)", + "label-delete-pop": "Non potrai tornare indietro. Procedendo, rimuoverai questa etichetta da tutte le schede e distruggerai la sua cronologia.", + "labels": "Etichette", + "language": "Lingua", + "last-admin-desc": "Non puoi cambiare i ruoli perché deve esserci almeno un admin.", + "leave-board": "Abbandona bacheca", + "leave-board-pop": "Sei sicuro di voler abbandonare __boardTitle__? Sarai rimosso da tutte le schede in questa bacheca.", + "leaveBoardPopup-title": "Abbandonare la bacheca?", + "link-card": "Link a questa scheda", + "list-archive-cards": "Sposta tutte le schede in questa lista nell'archivio", + "list-archive-cards-pop": "Questo rimuoverà tutte le schede nell'elenco dalla bacheca. Per vedere le schede nell'archivio e portarle dov'erano nella bacheca, clicca su \"Menu\" > \"Archivio\".", + "list-move-cards": "Sposta tutte le schede in questa lista", + "list-select-cards": "Seleziona tutte le schede in questa lista", + "set-color-list": "Imposta colore", + "listActionPopup-title": "Azioni lista", + "settingsUserPopup-title": "Impostazioni utente", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Azioni swimlane", + "swimlaneAddPopup-title": "Aggiungi swimlane sotto", + "listImportCardPopup-title": "Importa una scheda di Trello", + "listImportCardsTsvPopup-title": "Importa CSV/TSV di Excel", + "listMorePopup-title": "Altro", + "link-list": "Link a questa lista", + "list-delete-pop": "Tutte le azioni saranno rimosse dal flusso attività e non sarai in grado di recuperare la lista. Non potrai tornare indietro.", + "list-delete-suggest-archive": "Puoi spostare un elenco nell'archivio per rimuoverlo dalla bacheca e mantentere la sua attività.", + "lists": "Liste", + "swimlanes": "Swimlane", + "log-out": "Esci", + "log-in": "Accedi", + "loginPopup-title": "Accedi", + "memberMenuPopup-title": "Impostazioni membri", + "members": "Membri", + "menu": "Menu", + "move-selection": "Sposta elementi selezionati", + "moveCardPopup-title": "Sposta scheda", + "moveCardToBottom-title": "Sposta in fondo", + "moveCardToTop-title": "Sposta in cima", + "moveSelectionPopup-title": "Sposta elementi selezionati", + "multi-selection": "Selezione multipla", + "multi-selection-label": "Selezionare etichetta", + "multi-selection-member": "Selezionare membro", + "multi-selection-on": "Selezione multipla attiva", + "muted": "Silenziato", + "muted-info": "Non sarai mai notificato delle modifiche in questa bacheca", + "my-boards": "Le mie bacheche", + "name": "Nome", + "no-archived-cards": "Non ci sono schede nell'archivio.", + "no-archived-lists": "Non ci sono liste nell'archivio.", + "no-archived-swimlanes": "Non ci sono swimlane nell'archivio.", + "no-results": "Nessun risultato", + "normal": "Normale", + "normal-desc": "Può visionare e modificare le schede. Non può cambiare le impostazioni.", + "not-accepted-yet": "Invitato non ancora accettato", + "notify-participate": "Ricevi aggiornamenti per qualsiasi scheda a cui partecipi come creatore o membro", + "notify-watch": "Ricevi aggiornamenti per tutte le bacheche, liste o schede che stai seguendo", + "optional": "opzionale", + "or": "oppure", + "page-maybe-private": "Questa pagina potrebbe essere privata. Potresti essere in grado di vederla <a href='%s'>facendo il log-in</a>.", + "page-not-found": "Pagina non trovata.", + "password": "Password", + "paste-or-dragdrop": "per incollare, oppure trascina & rilascia il file immagine (solo immagini)", + "participating": "Partecipando", + "preview": "Anteprima", + "previewAttachedImagePopup-title": "Anteprima", + "previewClipboardImagePopup-title": "Anteprima", + "private": "Privata", + "private-desc": "Questa bacheca è privata. Solo le persone aggiunte alla bacheca possono vederla e modificarla.", + "profile": "Profilo", + "public": "Pubblica", + "public-desc": "Questa bacheca è pubblica. È visibile a chiunque abbia il link e sarà mostrata dai motori di ricerca come Google. Solo le persone aggiunte alla bacheca possono modificarla.", + "quick-access-description": "Stella una bacheca per aggiungere una scorciatoia in questa barra.", + "remove-cover": "Rimuovi cover", + "remove-from-board": "Rimuovi dalla bacheca", + "remove-label": "Rimuovi etichetta", + "listDeletePopup-title": "Eliminare lista?", + "remove-member": "Rimuovi utente", + "remove-member-from-card": "Rimuovi dalla scheda", + "remove-member-pop": "Rimuovere __name__ (__username__) da __boardTitle__? L'utente sarà rimosso da tutte le schede in questa bacheca. Riceveranno una notifica.", + "removeMemberPopup-title": "Rimuovere membro?", + "rename": "Rinomina", + "rename-board": "Rinomina bacheca", + "restore": "Ripristina", + "save": "Salva", + "search": "Cerca", + "rules": "Regole", + "search-cards": "Ricerca per titolo, descrizione scheda/lista e campi personalizzati su questa bacheca", + "search-example": "Scrivere il testo da ricercare e premere Invio", + "select-color": "Scegli un colore", + "select-board": "Seleziona bacheca", + "set-wip-limit-value": "Seleziona un limite per il massimo numero di attività in questa lista", + "setWipLimitPopup-title": "Imposta limite Work In Progress", + "shortcut-assign-self": "Assegna te stesso alla scheda corrente", + "shortcut-autocomplete-emoji": "Autocompletamento emoji", + "shortcut-autocomplete-members": "Autocompletamento membri", + "shortcut-clear-filters": "Pulisci tutti i filtri", + "shortcut-close-dialog": "Chiudi finestra di dialogo", + "shortcut-filter-my-cards": "Filtra le mie schede", + "shortcut-show-shortcuts": "Apri questa lista di scorciatoie", + "shortcut-toggle-filterbar": "Apri/chiudi la barra laterale dei filtri", + "shortcut-toggle-searchbar": "Apri/chiudi la barra laterale di ricerca", + "shortcut-toggle-sidebar": "Apri/chiudi la barra laterale della bacheca", + "show-cards-minimum-count": "Mostra il contatore delle schede se la lista ne contiene più di", + "sidebar-open": "Apri sidebar", + "sidebar-close": "Chiudi sidebar", + "signupPopup-title": "Crea un account", + "star-board-title": "Clicca per stellare questa bacheca. Sarà mostrata all'inizio della tua lista bacheche.", + "starred-boards": "Bacheche preferite", + "starred-boards-description": "Le bacheche stellate vengono mostrate in cima alla lista delle bacheche.", + "subscribe": "Iscriviti", + "team": "Team", + "this-board": "questa bacheca", + "this-card": "questa scheda", + "spent-time-hours": "Tempo trascorso (ore)", + "overtime-hours": "Overtime (ore)", + "overtime": "Overtime", + "has-overtime-cards": "Ci sono schede scadute", + "has-spenttime-cards": "Ci sono schede con tempo impiegato", + "time": "Ora", + "title": "Titolo", + "tracking": "Monitoraggio", + "tracking-info": "Sarai notificato per tutte le modifiche alle schede delle quali sei creatore o membro.", + "type": "Tipo", + "unassign-member": "Rimuovi membro", + "unsaved-description": "Hai una descrizione non salvata", + "unwatch": "Smetti di seguire", + "upload": "Carica", + "upload-avatar": "Carica un avatar", + "uploaded-avatar": "Avatar caricato", + "custom-top-left-corner-logo-image-url": "URL dell'immagine del logo personalizzato nell'angolo superiore sinistro", + "custom-top-left-corner-logo-link-url": "URL del link del logo personalizzato nell'angolo superiore sinistro", + "custom-top-left-corner-logo-height": "Altezza del logo personalizzato nell'angolo superiore sinistro. Default: 27", + "custom-login-logo-image-url": "URL dell'immagine del logo personalizzato per il login", + "custom-login-logo-link-url": "Link dell'immagine del logo personalizzato per il login", + "text-below-custom-login-logo": "Testo sotto il logo personalizzato per il login", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Importa username", + "view-it": "Vedi", + "warn-list-archived": "attenzione: questa scheda si trova in una lista nell'archivio", + "watch": "Segui", + "watching": "Stai seguendo", + "watching-info": "Sarai notificato per tutte le modifiche in questa bacheca", + "welcome-board": "Bacheca di benvenuto", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basi", + "welcome-list2": "Avanzate", + "card-templates-swimlane": "Template schede", + "list-templates-swimlane": "Template liste", + "board-templates-swimlane": "Template bacheche", + "what-to-do": "Cosa vuoi fare?", + "wipLimitErrorPopup-title": "Limite Work In Progress non valido", + "wipLimitErrorPopup-dialog-pt1": "Il numero di compiti in questa lista è maggiore del limite di work in progress che hai definito in precedenza.", + "wipLimitErrorPopup-dialog-pt2": "Per favore, sposta alcuni dei compiti fuori da questa lista, oppure imposta un limite di work in progress più alto.", + "admin-panel": "Pannello dell'amministratore", + "settings": "Impostazioni", + "people": "Persone", + "registration": "Registrazione", + "disable-self-registration": "Disabilita Auto-registrazione", + "invite": "Invita", + "invite-people": "Invita persone", + "to-boards": "Alla(e) bacheca", + "email-addresses": "Indirizzi email", + "smtp-host-description": "L'indirizzo del server SMTP che gestisce le tue email.", + "smtp-port-description": "La porta che il tuo server SMTP utilizza per le email in uscita.", + "smtp-tls-description": "Abilita supporto TLS per server SMTP", + "smtp-host": "Host SMTP", + "smtp-port": "Porta SMTP", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "Supporto TLS", + "send-from": "Da", + "send-smtp-test": "Invia un'email di test a te stesso", + "invitation-code": "Codice d'invito", + "email-invite-register-subject": "__inviter__ ti ha inviato un invito", + "email-invite-register-text": "Gentile __user__,\n\n__inviter__ ti ha invitato a partecipare a questa bacheca kanban per collaborare.\n\nPer favore segui il collegamento qui sotto:\n__url__\n\nIl tuo codice di invito è: __icode__\n\nGrazie.", + "email-smtp-test-subject": "E-Mail di test SMTP", + "email-smtp-test-text": "E-mail inviata correttamente", + "error-invitation-code-not-exist": "Il codice d'invito non esiste", + "error-notAuthorized": "Non sei autorizzato ad accedere a questa pagina.", + "webhook-title": "Nome webhook", + "webhook-token": "Token (facoltativo per l'autenticazione)", + "outgoing-webhooks": "Webhook in uscita", + "bidirectional-webhooks": "Webhook a due vie", + "outgoingWebhooksPopup-title": "Webhook in uscita", + "boardCardTitlePopup-title": "Filtro per titolo scheda", + "disable-webhook": "Disattiva questo webhook", + "global-webhook": "Webhook globali", + "new-outgoing-webhook": "Nuovo webhook in uscita", + "no-name": "(Sconosciuto)", + "Node_version": "Versione di Node", + "Meteor_version": "Versione Meteor", + "MongoDB_version": "Versione MongoDB", + "MongoDB_storage_engine": "Storage engine di MongoDB", + "MongoDB_Oplog_enabled": "MongoDB Oplog abilitato", + "OS_Arch": "Architettura SO", + "OS_Cpus": "Numero CPU SO", + "OS_Freemem": "Memoria libera SO", + "OS_Loadavg": "Carico medio SO", + "OS_Platform": "Piattaforma SO", + "OS_Release": "Versione di rilascio SO", + "OS_Totalmem": "Memoria totale SO", + "OS_Type": "Tipo SO", + "OS_Uptime": "Tempo di attività del SO", + "days": "giorni", + "hours": "ore", + "minutes": "minuti", + "seconds": "secondi", + "show-field-on-card": "Visualizza questo campo sulla scheda", + "automatically-field-on-card": "Aggiungi campo alle nuove schede", + "always-field-on-card": "Aggiungi campo a tutte le schede", + "showLabel-field-on-card": "Mostra l'etichetta di campo su minischeda", + "yes": "Sì", + "no": "No", + "accounts": "Profili", + "accounts-allowEmailChange": "Permetti modifica dell'email", + "accounts-allowUserNameChange": "Consenti la modifica del nome utente", + "createdAt": "Creato alle", + "modifiedAt": "Modificato il", + "verified": "Verificato", + "active": "Attivo", + "card-received": "Ricevuta", + "card-received-on": "Ricevuta il", + "card-end": "Fine", + "card-end-on": "Termina il", + "editCardReceivedDatePopup-title": "Cambia data ricezione", + "editCardEndDatePopup-title": "Cambia data finale", + "setCardColorPopup-title": "Imposta il colore", + "setCardActionsColorPopup-title": "Scegli un colore", + "setSwimlaneColorPopup-title": "Scegli un colore", + "setListColorPopup-title": "Scegli un colore", + "assigned-by": "Assegnato da", + "requested-by": "Richiesto da", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "L'eliminazione è permanente. Tutte le azioni, liste e schede associate a questa bacheca andranno perse.", + "delete-board-confirm-popup": "Tutte le liste, schede, etichette e azioni saranno rimosse e non sarai più in grado di recuperare il contenuto della bacheca. L'azione non è annullabile.", + "boardDeletePopup-title": "Eliminare la bacheca?", + "delete-board": "Elimina bacheca", + "default-subtasks-board": "Sottocompiti per la bacheca __board__", + "default": "Predefinito", + "queue": "Coda", + "subtask-settings": "Impostazioni sotto-compiti", + "card-settings": "Impostazioni della scheda", + "boardSubtaskSettingsPopup-title": "Impostazioni sotto-compiti della bacheca", + "boardCardSettingsPopup-title": "Impostazioni della scheda", + "deposit-subtasks-board": "Deposita i sotto compiti in questa bacheca", + "deposit-subtasks-list": "Lista di destinaizoni per questi sotto-compiti", + "show-parent-in-minicard": "Mostra genirotri nelle mini schede:", + "prefix-with-full-path": "Prefisso con percorso completo", + "prefix-with-parent": "Prefisso con genitore", + "subtext-with-full-path": "Sottotesto con percorso completo", + "subtext-with-parent": "Sotto-testo con genitore", + "change-card-parent": "Cambia la scheda genitore", + "parent-card": "Scheda genitore", + "source-board": "Bacheca d'origine", + "no-parent": "Non mostrare i genitori", + "activity-added-label": "ha aggiunto l'etichetta '%s' a %s", + "activity-removed-label": "ha rimosso l'etichetta '%s' da %s", + "activity-delete-attach": "rimosso un allegato da %s", + "activity-added-label-card": "aggiunta etichetta '%s'", + "activity-removed-label-card": "L' etichetta '%s' è stata rimossa.", + "activity-delete-attach-card": "cancella un allegato", + "activity-set-customfield": "imposta campo personalizzato '%s' a '%s' in %s", + "activity-unset-customfield": "campo personalizzato non impostato '%s' in %s", + "r-rule": "Regola", + "r-add-trigger": "Aggiungi trigger", + "r-add-action": "Aggiungi azione", + "r-board-rules": "Regole della bacheca", + "r-add-rule": "Aggiungi regola", + "r-view-rule": "Visualizza regola", + "r-delete-rule": "Elimina regola", + "r-new-rule-name": "Titolo nuova regola", + "r-no-rules": "Nessuna regola", + "r-trigger": "Trigger", + "r-action": "Azione", + "r-when-a-card": "Quando una scheda", + "r-is": "è", + "r-is-moved": "viene spostata", + "r-added-to": "Aggiunto/a a", + "r-removed-from": "Rimosso da", + "r-the-board": "la bacheca", + "r-list": "lista", + "list": "Lista", + "set-filter": "Imposta un filtro", + "r-moved-to": "Spostato/a a", + "r-moved-from": "Spostato/a da", + "r-archived": "Spostato/a nell'archivio", + "r-unarchived": "Ripristinato/a dall'archivio", + "r-a-card": "una scheda", + "r-when-a-label-is": "Quando un'etichetta viene", + "r-when-the-label": "Quando l'etichetta viene", + "r-list-name": "nome lista", + "r-when-a-member": "Quando un membro viene", + "r-when-the-member": "Quando un membro viene", + "r-name": "nome", + "r-when-a-attach": "Quando un allegato", + "r-when-a-checklist": "Quando una checklist è", + "r-when-the-checklist": "Quando la checklist", + "r-completed": "Completato/a", + "r-made-incomplete": "Rendi incompleto", + "r-when-a-item": "Quando un elemento della checklist è", + "r-when-the-item": "Quando un elemento della checklist", + "r-checked": "Selezionato", + "r-unchecked": "Deselezionato", + "r-move-card-to": "Sposta scheda a", + "r-top-of": "Al di sopra di", + "r-bottom-of": "Al di sotto di", + "r-its-list": "la sua lista", + "r-archive": "Sposta nell'archivio", + "r-unarchive": "Ripristina dall'archivio", + "r-card": "scheda", + "r-add": "Aggiungi", + "r-remove": "Rimuovi", + "r-label": "etichetta", + "r-member": "membro", + "r-remove-all": "Rimuovi tutti i membri dalla scheda", + "r-set-color": "Imposta il colore", + "r-checklist": "checklist", + "r-check-all": "Seleziona tutti", + "r-uncheck-all": "Togli la spunta a tutti", + "r-items-check": "Elementi della checklist", + "r-check": "Spunta", + "r-uncheck": "Togli la spunta", + "r-item": "elemento", + "r-of-checklist": "della lista di cose da fare", + "r-send-email": "Invia un'e-mail", + "r-to": "a", + "r-of": "di", + "r-subject": "soggetto", + "r-rule-details": "Dettagli della regola", + "r-d-move-to-top-gen": "Sposta la scheda in cima alla sua lista", + "r-d-move-to-top-spec": "Sposta la scheda in cima alla lista", + "r-d-move-to-bottom-gen": "Sposta la scheda in fondo alla sua lista", + "r-d-move-to-bottom-spec": "Muovi la scheda in fondo alla lista", + "r-d-send-email": "Invia email", + "r-d-send-email-to": "a", + "r-d-send-email-subject": "oggetto", + "r-d-send-email-message": "messaggio", + "r-d-archive": "Sposta scheda nell'archivio", + "r-d-unarchive": "Ripristina la scheda dall'archivio", + "r-d-add-label": "Aggiungi etichetta", + "r-d-remove-label": "Rimuovi etichetta", + "r-create-card": "Crea una nuova scheda", + "r-in-list": "in lista", + "r-in-swimlane": "nella swimlane", + "r-d-add-member": "Aggiungi membro", + "r-d-remove-member": "Rimuovi membro", + "r-d-remove-all-member": "Rimuovi tutti i membri", + "r-d-check-all": "Seleziona tutti gli item di una lista", + "r-d-uncheck-all": "Deseleziona tutti gli items di una lista", + "r-d-check-one": "Seleziona elemento", + "r-d-uncheck-one": "Deseleziona elemento", + "r-d-check-of-list": "della lista di cose da fare", + "r-d-add-checklist": "Aggiungi checklist", + "r-d-remove-checklist": "Rimuovi checklist", + "r-by": "da", + "r-add-checklist": "Aggiungi checklist", + "r-with-items": "con elementi", + "r-items-list": "elemento1,elemento2,elemento3", + "r-add-swimlane": "Aggiungi swimlane", + "r-swimlane-name": "nome swimlane", + "r-board-note": "Nota: Lascia un campo vuoto per abbinare ogni possibile valore", + "r-checklist-note": "Nota: Gli elementi della checklist devono essere scritti come valori separati dalla virgola", + "r-when-a-card-is-moved": "Quando una scheda viene spostata in un'altra lista", + "r-set": "Imposta", + "r-update": "Aggiorna", + "r-datefield": "campo data", + "r-df-start-at": "inizio", + "r-df-due-at": "scadenza", + "r-df-end-at": "fine", + "r-df-received-at": "ricevuta", + "r-to-current-datetime": "a data/ora corrente", + "r-remove-value-from": "Rimuovi valore da", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Metodo di autenticazione", + "authentication-type": "Tipo di autenticazione", + "custom-product-name": "Nome prodotto personalizzato", + "layout": "Layout", + "hide-logo": "Nascondi il logo", + "add-custom-html-after-body-start": "Aggiungi codice HTML personalizzato dopo <body> inzio", + "add-custom-html-before-body-end": "Aggiunti il codice HTML prima di </body> fine", + "error-undefined": "Qualcosa è andato storto", + "error-ldap-login": "C'è stato un errore mentre provavi ad effettuare il login", + "display-authentication-method": "Mostra il metodo di autenticazione", + "default-authentication-method": "Metodo di autenticazione predefinito", + "duplicate-board": "Duplica bacheca", + "org-number": "Il numero di organizzazioni è:", + "team-number": "Il numero di team è:", + "people-number": "Il numero di persone è:", + "swimlaneDeletePopup-title": "Eliminare la swimlane?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Ripristina tutto", + "delete-all": "Elimina tutto", + "loading": "Caricamento in corso, attendere...", + "previous_as": "l'ultima volta è stata", + "act-a-dueAt": "Scadenza modificata in __timeValue__\nData precedente: __timeOldValue__", + "act-a-endAt": "orario finale modificato in __timeValue__ (precedentemente: __timeOldValue__)", + "act-a-startAt": "orario iniziale modificato in __timeValue__ (precedentemente: __timeOldValue__)", + "act-a-receivedAt": "orario di ricezione modificato in __timeValue__ (precedentemente: __timeOldValue__)", + "a-dueAt": "scadenza modificata in", + "a-endAt": "orario finale modificato in", + "a-startAt": "orario iniziale modificato in", + "a-receivedAt": "orario di ricezione modificato in", + "almostdue": "la data di scadenza attuale %s si sta avvicinando", + "pastdue": "la data di scadenza attuale %s è scaduta", + "duenow": "la data di scadenza attuale %s è oggi", + "act-newDue": "__list__/__card__ ha un 1° sollecito [__board__]", + "act-withDue": "sollecito relativo a __list__/__card__ [__board__]", + "act-almostdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è vicina", + "act-pastdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è già passata", + "act-duenow": "sollecito inviato: la scadenza (__timeValue__) di __card__ è adesso", + "act-atUserComment": "Sei stato menzionato in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Sei sicuro di voler cancellare questo profilo? Non sarà possibile ripristinarlo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Permetti agli utenti di cancellare il loro profilo", + "hide-minicard-label-text": "Nascondi etichetta minicard", + "show-desktop-drag-handles": "Mostra maniglie di trascinamento del desktop", + "assignee": "Assegnatario", + "cardAssigneesPopup-title": "Assegnatario", + "addmore-detail": "Aggiungi una descrizione più dettagliata", + "show-on-card": "Mostra sulla scheda", + "new": "Nuovo", + "editOrgPopup-title": "Modifica Organizzazione", + "newOrgPopup-title": "Nuova Organizzazione", + "editTeamPopup-title": "Modifica Team", + "newTeamPopup-title": "Nuovo Team", + "editUserPopup-title": "Modifica utente", + "newUserPopup-title": "Nuovo utente", + "notifications": "Notifiche", + "view-all": "Mostra tutte", + "filter-by-unread": "Filtra per non letto", + "mark-all-as-read": "Segna tutto come letto", + "remove-all-read": "Rimuovi tutti i già letti", + "allow-rename": "Consenti Rinomina", + "allowRenamePopup-title": "Consenti Rinomina", + "start-day-of-week": "Imposta l'inizio del giorno della settimana", + "monday": "Lunedì", + "tuesday": "Martedì", + "wednesday": "Mercoledì", + "thursday": "Giovedì", + "friday": "Venerdì", + "saturday": "Sabato", + "sunday": "Domenica", + "status": "Stato", + "swimlane": "Swimlane", + "owner": "Proprietario", + "last-modified-at": "Ultima modifica il", + "last-activity": "Ultima attività", + "voting": "Votazione", + "archived": "Archiviato", + "delete-linked-card-before-this-card": "Non puoi eliminare questa scheda prima di avere eliminato una scheda collegata che ha", + "delete-linked-cards-before-this-list": "Non puoi eliminare questa lista prima di avere eliminato le schede collegate che puntano su schede in questa lista", + "hide-checked-items": "Nascondi articoli controllati", + "task": "Compito", + "create-task": "Crea compito", + "ok": "OK", + "organizations": "Organizzazioni", + "teams": "Teams", + "displayName": "Nome da visualizzare", + "shortName": "Nome corto", + "website": "Sito Web", + "person": "Persona", + "my-cards": "Le mie schede", + "card": "Scheda", + "board": "Bacheca", + "context-separator": "/", + "myCardsSortChange-title": "Ordina le mie schede...", + "myCardsSortChangePopup-title": "Ordina le mie schede...", + "myCardsSortChange-choice-board": "Per bacheca", + "myCardsSortChange-choice-dueat": "Per data di scadenza", + "dueCards-title": "Schede in scadenza", + "dueCardsViewChange-title": "Vista schede con scadenza", + "dueCardsViewChangePopup-title": "Vista schede con scadenza", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "Tutti gli utenti", + "dueCardsViewChange-choice-all-description": "Visualizza tutte le schede non completate con una data di *scadenza* dalle bacheche in cui l'utente ha il permesso.", + "broken-cards": "Schede danneggiate", + "board-title-not-found": "Bacheca '%s' non trovata.", + "swimlane-title-not-found": "Swimlane '%s' non trovata.", + "list-title-not-found": "Lista '%s' non trovata.", + "label-not-found": "Etichetta '%s' non trovata.", + "label-color-not-found": "Colore etichetta %s non trovato.", + "user-username-not-found": "Username '%s' non trovato.", + "comment-not-found": "Schede con commenti contenenti il testo '%s' non trovate.", + "globalSearch-title": "Cerca in tutte le bacheche", + "no-cards-found": "Nessuna scheda trovata", + "one-card-found": "Una scheda trovata", + "n-cards-found": "%s scheda trovata", + "n-n-of-n-cards-found": "__start__-__end__ di __total__ schede trovate", + "operator-board": "bacheca", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "etichetta", + "operator-label-abbrev": "#", + "operator-user": "utente", + "operator-user-abbrev": "@", + "operator-member": "membro", + "operator-member-abbrev": "m", + "operator-assignee": "assegnatario", + "operator-assignee-abbrev": "a", + "operator-creator": "creatore", + "operator-status": "stato", + "operator-due": "scadenza", + "operator-created": "creato", + "operator-modified": "modificato", + "operator-sort": "ordina", + "operator-comment": "commento", + "operator-has": "possiede", + "operator-limit": "limite", + "predicate-archived": "archiviato", + "predicate-open": "aperto/a", + "predicate-ended": "finito", + "predicate-all": "tutto", + "predicate-overdue": "in ritardo", + "predicate-week": "settimana", + "predicate-month": "mese", + "predicate-quarter": "trimestre", + "predicate-year": "anno", + "predicate-due": "scadenza", + "predicate-modified": "modificato", + "predicate-created": "creato", + "predicate-attachment": "allegato", + "predicate-description": "descrizione", + "predicate-checklist": "checklist", + "predicate-start": "inizio", + "predicate-end": "fine", + "predicate-assignee": "assegnatario", + "predicate-member": "membro", + "predicate-public": "pubblico", + "predicate-private": "privato", + "operator-unknown-error": "%s non è un operatore", + "operator-number-expected": "l'operatore __operator__ prevede un numero, invece è stato fornito '__value__'", + "operator-sort-invalid": "L'ordinamento di '%s' non è valido", + "operator-status-invalid": "'%s' non è uno stato valido", + "operator-has-invalid": "%s non è un controllo di esistenza valido", + "operator-limit-invalid": "%s non è un limite valido. Il limite dovrebbe essere un numero intero positivo.", + "next-page": "Pagina successiva", + "previous-page": "Pagina precedente", + "heading-notes": "Note", + "globalSearch-instructions-heading": "Istruzioni ricerca", + "globalSearch-instructions-description": "Per affinare le ricerche si possono includere degli operatori. Questi vanno specificati indicandone il nome ed il valore separati dai due punti. Ad esempio, specificare `list:Blocked` limita la ricerca alle schede contenute nella lista denominata *Blocked*. Se il valore del criterio contiene spazi o caratteri speciali, va racchiuso tra virgolette (es.: `__operator_list__:\"Da Controllare\"`)", + "globalSearch-instructions-operators": "Operatori disponibili:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - schede nelle bacheche corrispondenti a *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - schede nelle liste corrispondenti a *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - schede in swimlane corrispondenti a *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - schede con commenti contenenti *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - schede aventi un'etichetta corrispondente a *<color>* oppure *<name>*", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - schede dove *<username>* è un *membro* oppure *assegnatario*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - abbreviazione per `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - schede dove *<username>* è un *membro*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - schede dove *<username>* è un *assegnatario*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - schede create *<n>* giorni fa o prima", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - schede modificate *<n>* giorni fa o prima", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - dove *<status>* è uno dei seguenti:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - schede archiviate", + "globalSearch-instructions-status-all": "`__predicate_all__` - tutte le schede archiviate e non archiviate", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - schede aventi una data di fine", + "globalSearch-instructions-status-public": "`__predicate_public__` - schede soltanto in bacheche pubbliche", + "globalSearch-instructions-status-private": "`__predicate_private__` - schede soltanto in bacheche private", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - dove *<field>* è uno di `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` oppure `__predicate_member__`. Inserendo `-` davanti a *<field>* cerca per l'assenza di un valore nel campo (per esempio `has:-due` cerca per schede senza una data di scadenza).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - dove *<sort-name>* è uno dei `__predicate_due__`, `__predicate_created__` oppure `__predicate_modified__`. Per un ordine discendente, aggiungi un `-` davanti al nome.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - dove *<n>* è un numero intero positivo che esprime il numero delle schede da visualizzare per pagina.", + "globalSearch-instructions-notes-1": "È possibile specificare più operatori.", + "globalSearch-instructions-notes-2": "Valori diversi dello stesso criterio di ricerca vengono combinati tramite *OR*. Vengono quindi selezionate tutte le schede che soddisfano almeno uno dei criteri. `__operator_list__:Available __operator_list__:Blocked` selezionerà le schede contenute in qualsiasi lista chiamata *Blocked* o *Available*.", + "globalSearch-instructions-notes-3": "Più criteri di ricerca sono combinati con l'operatore *AND*. Vengono selezionate solo le schede che soddisfano tutti gli operatori. `__operator_list__:Available __operator_label__:red` seleziona solo le schede nella lista *Available* che hanno una etichetta *red*.", + "globalSearch-instructions-notes-3-2": "I giorni possono essere specificati come numeri interi positivi/negativi oppure utilizzando `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` oppure `__predicate_year__` per il periodo corrente.", + "globalSearch-instructions-notes-4": "La distinzione tra maiuscole e minuscole non viene effettuata nelle ricerche di testo.", + "globalSearch-instructions-notes-5": "Per impostazione predefinita le schede archiviate non sono incluse nella ricerca.", + "link-to-search": "Link a questa ricerca", + "excel-font": "Arial", + "number": "Numero", + "label-colors": "Colori etichette", + "label-names": "Nomi etichette", + "archived-at": "archiviata il", + "sort-cards": "Ordina schede", + "cardsSortPopup-title": "Ordina schede", + "due-date": "Data di scadenza", + "server-error": "Errore server", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Titolo (ordine alfabetico)", + "created-at-newest-first": "Data di creazione (dalla più nuova)", + "created-at-oldest-first": "Data di creazione (dalla più vecchia)", + "links-heading": "Link", + "hide-system-messages-of-all-users": "Nascondi messaggi di sistema di tutti gli utenti", + "now-system-messages-of-all-users-are-hidden": "Messaggi di sistema di tutti gli utenti nascosti", + "move-swimlane": "Sposta swimlane", + "moveSwimlanePopup-title": "Sposta swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creatore", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/ja.i18n.json b/i18n/ja.i18n.json index 7195e6f1c..c7c076e2a 100644 --- a/i18n/ja.i18n.json +++ b/i18n/ja.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "チェックリスト %s が未完了になりました", "activity-editComment": "コメント %s を編集しました", "activity-deleteComment": "コメント %s を削除しました", + "activity-receivedDate": "受付日を %s に変更しました / %s", + "activity-startDate": "開始日を %s に変更しました / %s", + "activity-dueDate": "期限日を %s に変更しました / %s", + "activity-endDate": "終了日を %s に変更しました / %s", "add-attachment": "添付ファイルを追加", "add-board": "ボードを追加", + "add-template": "Add Template", "add-card": "カードを追加", + "add-card-to-top-of-list": "カードをリストの先頭に追加", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "スイムレーンを追加", "add-subtask": "サブタスクを追加", "add-checklist": "チェックリストを追加", @@ -113,6 +120,8 @@ "archives": "アーカイブ", "template": "テンプレート", "templates": "テンプレート", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "メンバーの割当", "attached": "添付されました", "attachment": "添付ファイル", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "添付ファイルを削除しますか?", "attachments": "添付ファイル", "auto-watch": "作成されたボードを自動的にウォッチする", - "avatar-too-big": "アバターが大きすぎます(最大70KB)", + "avatar-too-big": "アバターが大きすぎます(最大520KB)", "back": "戻る", "board-change-color": "色の変更", "board-nb-stars": "%s stars", "board-not-found": "ボードが見つかりません", "board-private-info": "ボードは <strong>非公開</strong> になります。", "board-public-info": "ボードは公開されます。", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "ボードの背景を変更", "boardChangeTitlePopup-title": "ボード名の変更", "boardChangeVisibilityPopup-title": "公開範囲の変更", @@ -138,6 +148,7 @@ "board-view-cal": "カレンダー", "board-view-swimlanes": "スイムレーン", "board-view-collapse": "折りたたむ", + "board-view-gantt": "ガント", "board-view-lists": "リスト", "bucket-example": "例:バケットリスト", "cancel": "キャンセル", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "添付元", "cardCustomField-datePopup-title": "日時変更", "cardCustomFieldsPopup-title": "カスタムフィールドの編集", + "cardStartVotingPopup-title": "投票を開始", + "positiveVoteMembersPopup-title": "支持者", + "negativeVoteMembersPopup-title": "反対者", + "card-edit-voting": "投票を編集", + "editVoteEndDatePopup-title": "投票終了日の変更", + "allowNonBoardMembers": "ログイン中のすべてのユーザーに許可", + "vote-question": "投票の質問事項", + "vote-public": "誰が何に投票したか表示", + "vote-for-it": "賛成", + "vote-against": "反対", + "deleteVotePopup-title": "投票を削除しますか?", + "vote-delete-pop": "投票は取り消しできません。この投票に関係するすべてのアクションがなくなります。", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "カードを削除しますか?", "cardDetailsActionsPopup-title": "カード操作", "cardLabelsPopup-title": "ラベル", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "テンプレートの作成", "cards": "カード", "cards-count": "カード", + "cards-count-one": "カード", "casSignIn": "CASでサインインする", "cardType-card": "カード", "cardType-linkedCard": "リンクされたカード", @@ -191,6 +236,7 @@ "close": "閉じる", "close-board": "ボードを閉じる", "close-board-pop": "ホームのヘッダ上にあるアーカイブボタンをクリックするとボードをリストアできます。", + "close-card": "Close Card", "color-black": "黒", "color-blue": "青", "color-crimson": "濃赤", @@ -244,6 +290,8 @@ "current": "現在", "custom-field-delete-pop": "この操作は取り消しできません。このカスタムフィールドはすべてのカードから外され履歴からも見えなくなります。", "custom-field-checkbox": "チェックボックス", + "custom-field-currency": "通貨", + "custom-field-currency-option": "通貨コード", "custom-field-date": "日付", "custom-field-dropdown": "ドロップダウンリスト", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "操作にはボードメンバーである必要があります", "error-json-malformed": "このテキストは、有効なJSON形式ではありません", "error-json-schema": "JSONデータが不正な値を含んでいます", + "error-csv-schema": "CSV(カンマ区切り)/TSV(タブ区切り)の書式が不正です", "error-list-doesNotExist": "このリストは存在しません", "error-user-doesNotExist": "ユーザーが存在しません", "error-user-notAllowSelf": "自分を招待することはできません。", "error-user-notCreated": "ユーザーが作成されていません", "error-username-taken": "このユーザ名は既に使用されています", + "error-orgname-taken": "この組織名はすでに使われています", + "error-teamname-taken": "このチーム名はすでに使われています", "error-email-taken": "メールは既に受け取られています", "export-board": "ボードのエクスポート", + "export-board-json": "ボードをJSONにエクスポート", + "export-board-csv": "ボードをCSVにエクスポート", + "export-board-tsv": "ボードをTSVにエクスポート", + "export-board-excel": "ボードをExcelにエクスポート", + "user-can-not-export-excel": "ユーザーはExcelにエクスポートできません ", + "export-board-html": "ボードをHTMLにエクスポート", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "ボードのエクスポート", + "exportCardPopup-title": "Export card", "sort": "並べ替え", "sort-desc": "クリックでリストをソート", "list-sort-by": "次によりリストを並べ替え:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "フィルター", "filter-cards": "カードまたはリストをフィルタ", + "filter-dates-label": "日付によるフィルタ", + "filter-no-due-date": "期限なし", + "filter-overdue": "期限切れ", + "filter-due-today": "本日期限", + "filter-due-this-week": "今週期限", + "filter-due-tomorrow": "明日期限", "list-filter-label": "タイトルでリストをフィルタ", "filter-clear": "フィルターの解除", + "filter-labels-label": "ラベルによるフィルタ", "filter-no-label": "ラベルなし", + "filter-member-label": "メンバーによるフィルタ", "filter-no-member": "メンバーなし", + "filter-assignee-label": "担当書によるフィルタ", + "filter-no-assignee": "担当者なし", + "filter-custom-fields-label": "カスタムフィールドによるフィルタ", "filter-no-custom-fields": "カスタムフィールドなし", "filter-show-archive": "アーカイブされたリストを表示", "filter-hide-empty": "空のリストを隠す", "filter-on": "フィルター有効", "filter-on-desc": "このボードのカードをフィルターしています。フィルターを編集するにはこちらをクリックしてください。", "filter-to-selection": "フィルターした項目を全選択", + "other-filters-label": "その他のフィルタ", "advanced-filter-label": "高度なフィルター", "advanced-filter-description": "高度なフィルタでは次のような演算子を使用できます:== != <= >= && || ( )\n半角スペースは演算子の区切り文字として使用します。\n\nフィールド名や値を使用したフィルタが可能です。\n例:Field1 == Value1\n\n注意:フィールド名や値にスペースが含まれる場合、それらをシングルクォーテーションで囲む必要があります。\n例:'Field 1' == 'Value 1'\n\n単体の制御文字 (' \\/) は無視されますので、\\を使用することができます。\n例:Field1 == I\\'m\n\n複数の条件を組み合わせることもできます。\n例:F1 == V1 || F1 == V2\n\n基本的にすべての演算子は左から右に評価されます。\n丸カッコを使用することで順序を変更できます。\n例:F1 == V1 && ( F2 == V2 || F2 == V3 )\n\nテキストフィールドでは正規表現を使用した検索もできます。\n例:F1 == /Tes.*/i", "fullname": "フルネーム", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "ボードの作成", "home": "ホーム", "import": "インポート", + "impersonate-user": "このユーザーとして成り代わる", "link": "リンク", "import-board": "ボードをインポート", "import-board-c": "ボードをインポート", "import-board-title-trello": "Trelloからボードをインポート", "import-board-title-wekan": "以前のエクスポートからボードをインポート", - "import-sandstorm-backup-warning": "この操作が完了して再度開くことができるのを確認するまでインポート元のボードまたはTrelloのデータを削除しないでください。「ボードが見つかりません」が表示された場合、データが失われたことを意味します。", - "import-sandstorm-warning": "ボードのインポートは、既存ボードのすべてのデータを置き換えます。", + "import-board-title-csv": "ボードをCSV/TSVからインポート", "from-trello": "Trelloから", "from-wekan": "以前のエクスポートから", + "from-csv": "CSV/TSV から", "import-board-instruction-trello": "Trelloボードの、 'Menu' → 'More' → 'Print and Export' → 'Export JSON'を選択し、テキストをコピーしてください。", + "import-board-instruction-csv": "カンマ区切り(CSV) / タブ区切り(TSV) ファイルに貼り付けてください。", "import-board-instruction-wekan": "ボードの、 'メニュー' → 'ボードのエクスポート'を選択し、ダウンロードされたファイルの中のテキストをコピーしてください。", "import-board-instruction-about-errors": "ボードのインポート中にエラーが発生した場合、インポートがまだ進行中のまま、全てのボードページに表示されている場合があります。", "import-json-placeholder": "JSONデータをここに貼り付けする", + "import-csv-placeholder": "CSV/TSVデータをここに貼り付けする", "import-map-members": "メンバーを紐付け", "import-members-map": "インポートしたボードにはいくつかのメンバーが含まれています。インポートしたいメンバーをユーザーにマッピングしてください", + "import-members-map-note": "注意:マップされていないメンバーは現在のユーザーに割り当てられます", "import-show-user-mapping": "メンバー紐付けの確認", "import-user-select": "このメンバーとして使用したいユーザーを選択してください", "importMapMembersAddPopup-title": "メンバーを選択", @@ -375,9 +453,13 @@ "list-select-cards": "リストの全カードを選択", "set-color-list": "色を選択", "listActionPopup-title": "操作一覧", + "settingsUserPopup-title": "ユーザー設定", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "スイムレーン操作", "swimlaneAddPopup-title": "直下にスイムレーンを追加", "listImportCardPopup-title": "Trelloのカードをインポート", + "listImportCardsTsvPopup-title": "Excel CSV/TSVからインポート", "listMorePopup-title": "さらに見る", "link-list": "このリストへのリンク", "list-delete-pop": "すべての内容がアクティビティから削除されます。この削除は元に戻すことができません。", @@ -396,6 +478,8 @@ "moveCardToTop-title": "先頭に移動", "moveSelectionPopup-title": "選択箇所に移動", "multi-selection": "複数選択", + "multi-selection-label": "選択したものにラベルを設定", + "multi-selection-member": "選択したものにメンバーを設定", "multi-selection-on": "複数選択有効", "muted": "ミュート", "muted-info": "このボードの変更は通知されません", @@ -440,9 +524,10 @@ "save": "保存", "search": "検索", "rules": "ルール", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "検索文字", + "search-cards": "このボード上のカード/リストタイトル、詳細、カスタムフィールドから検索", + "search-example": "検索文字列を入力してエンターを押してください", "select-color": "色を選択", + "select-board": "Select Board", "set-wip-limit-value": "このリスト中のタスクの最大数を設定", "setWipLimitPopup-title": "仕掛中制限設定", "shortcut-assign-self": "自分をこのカードに割り当てる", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "カードをフィルター", "shortcut-show-shortcuts": "このショートカットリストを表示する", "shortcut-toggle-filterbar": "フィルターサイドバーの切り替え", + "shortcut-toggle-searchbar": "検索サイドバーの切り替え", "shortcut-toggle-sidebar": "ボードサイドバーの切り替え", "show-cards-minimum-count": "以下より多い場合、リストにカード数を表示", "sidebar-open": "サイドバーを開く", @@ -481,7 +567,15 @@ "upload": "アップロード", "upload-avatar": "アバターのアップロード", "uploaded-avatar": "アップロードされたアバター", + "custom-top-left-corner-logo-image-url": "カスタムの左上ロゴイメージのURL", + "custom-top-left-corner-logo-link-url": "カスタムの左上ロゴイメージのリンクURL", + "custom-top-left-corner-logo-height": "カスタムの左上ロゴイメージの高さ。デフォルト:27", + "custom-login-logo-image-url": "カスタムのログインロゴイメージのURL", + "custom-login-logo-link-url": "カスタムのログインロゴイメージのリンクURL", + "text-below-custom-login-logo": "カスタムログインロゴイメージの下のテキスト", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "ユーザー名", + "import-usernames": "インポートするユーザー名", "view-it": "見る", "warn-list-archived": "警告:このカードはアーカイブされたリスト内にあります", "watch": "ウォッチ", @@ -553,7 +647,8 @@ "minutes": "分", "seconds": "秒", "show-field-on-card": "このフィールドをカードに表示", - "automatically-field-on-card": "全カードにフィールドを自動作成", + "automatically-field-on-card": "フィールドを新しいカードに追加", + "always-field-on-card": "フィールドをすべてのカードに追加", "showLabel-field-on-card": "ミニカード上のフィールドラベル表示", "yes": "はい", "no": "いいえ", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "メールアドレスの変更を許可", "accounts-allowUserNameChange": "ユーザー名の変更を許可", "createdAt": "作成日時", + "modifiedAt": "Modified at", "verified": "認証状況", "active": "有効状態", "card-received": "受付", @@ -575,6 +671,7 @@ "setListColorPopup-title": "色を選択", "assigned-by": "任命者", "requested-by": "依頼者", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "永久に削除されます。このボードに関連するリスト、カード、アクションはすべて失われます。", "delete-board-confirm-popup": "すべてのリスト、カード、ラベル、アクティビティは削除され、ボードの内容を元に戻すことができません。", "boardDeletePopup-title": "ボードを削除しますか?", @@ -614,13 +711,16 @@ "r-delete-rule": "ルールを削除", "r-new-rule-name": "新しいルールのタイトル", "r-no-rules": "ルールなし", + "r-trigger": "トリガー", + "r-action": "操作", "r-when-a-card": "カード:", "r-is": "が", "r-is-moved": "が移動された時", "r-added-to": "次に追加された時", "r-removed-from": "次から削除された時", "r-the-board": "ボード:", - "r-list": "リスト:", + "r-list": "リスト", + "list": "List", "set-filter": "フィルタを設定", "r-moved-to": "次に移動した時", "r-moved-from": "次から移動された時", @@ -665,6 +765,7 @@ "r-of-checklist": "チェックリスト", "r-send-email": "メールを送る", "r-to": "宛先", + "r-of": "/", "r-subject": "件名", "r-rule-details": "ルール詳細", "r-d-move-to-top-gen": "カードを自身のリストの先頭に移動", @@ -725,6 +826,8 @@ "display-authentication-method": "認証方式を表示", "default-authentication-method": "デフォルトの認証方式", "duplicate-board": "ボードの複製", + "org-number": "組織のメンバー数:", + "team-number": "チームのメンバー数:", "people-number": "メンバー数:", "swimlaneDeletePopup-title": "スイムレーンを削除しますか?", "swimlane-delete-pop": "すべての内容がアクティビティから削除されます。この削除は元に戻すことができません。", @@ -750,6 +853,8 @@ "act-duenow": "__card__ の期限日時 (__timeValue__) になりました", "act-atUserComment": "あなたが [__board__] __list__/__card__ に追記しました", "delete-user-confirm-popup": "本当にこのアカウントを削除しますか?この操作は取り消しできません。", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "ユーザー自身のアカウント削除を許可", "hide-minicard-label-text": "ミニカードのラベル名を隠す", "show-desktop-drag-handles": "デスクトップへのドラッグハンドルを表示", @@ -758,12 +863,200 @@ "addmore-detail": "詳細説明の追加", "show-on-card": "カードに表示する項目", "new": "新規作成", + "editOrgPopup-title": "組織を編集", + "newOrgPopup-title": "新規組織", + "editTeamPopup-title": "チームを編集", + "newTeamPopup-title": "新規チーム", "editUserPopup-title": "ユーザーを編集", "newUserPopup-title": "新規ユーザー", "notifications": "通知", "view-all": "全てを表示", "filter-by-unread": "未読でフィルタ", "mark-all-as-read": "全て既読にする", + "remove-all-read": "全ての既読を削除", "allow-rename": "リネームを許可する", - "allowRenamePopup-title": "リネームを許可する" + "allowRenamePopup-title": "リネームを許可する", + "start-day-of-week": "週の始まりを設定", + "monday": "月曜", + "tuesday": "火曜", + "wednesday": "水曜", + "thursday": "木曜", + "friday": "金曜", + "saturday": "土曜", + "sunday": "日曜", + "status": "ステータス", + "swimlane": "スイムレーン", + "owner": "オーナー", + "last-modified-at": "最終更新", + "last-activity": "最終アクティビティ", + "voting": "投票", + "archived": "アーカイブ", + "delete-linked-card-before-this-card": "カード内にある、リンクされているカードを削除しなければ、このカードは削除できません", + "delete-linked-cards-before-this-list": "リスト内にある、他のカードを参照しているカードを削除しなければ、このリストは削除できません", + "hide-checked-items": "チェックした項目を隠す", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "組織", + "teams": "チーム", + "displayName": "表示名", + "shortName": "短縮名", + "website": "ウェブサイト", + "person": "Person", + "my-cards": "自分のカード", + "card": "カード", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "カードの並び替え", + "myCardsSortChangePopup-title": "カードの並び替え", + "myCardsSortChange-choice-board": "ボード毎", + "myCardsSortChange-choice-dueat": "期限日毎", + "dueCards-title": "期限切れカード", + "dueCardsViewChange-title": "カードの表示", + "dueCardsViewChangePopup-title": "カードの表示", + "dueCardsViewChange-choice-me": "自分", + "dueCardsViewChange-choice-all": "全ユーザー", + "dueCardsViewChange-choice-all-description": "ユーザーに権限のあるボードから、期限が切れたすべての未完了のカードを表示します。", + "broken-cards": "壊れたカード", + "board-title-not-found": "ボード「%s」は見つかりませんでした。", + "swimlane-title-not-found": "スイムレーン「%s」は見つかりませんでした。", + "list-title-not-found": "リスト「%s」は見つかりませんでした。", + "label-not-found": "ラベル「%s」は見つかりませんでした。", + "label-color-not-found": "ラベル色 %s は見つかりませんでした。", + "user-username-not-found": "ユーザー名「%s」は見つかりませんでした。", + "comment-not-found": "コメントの文字列に「%s」が含まれるカードは見つかりませんでした。", + "globalSearch-title": "すべてのボードから検索", + "no-cards-found": "カードが見つかりませんでした", + "one-card-found": "1件のカードが見つかりました", + "n-cards-found": "%s件のカードが見つかりました", + "n-n-of-n-cards-found": "__start__-__end__ / __total__ 件のカードが見つかりました", + "operator-board": "ボード", + "operator-board-abbrev": "b", + "operator-swimlane": "スイムレーン", + "operator-swimlane-abbrev": "s", + "operator-list": "リスト", + "operator-list-abbrev": "l", + "operator-label": "ラベル", + "operator-label-abbrev": "#", + "operator-user": "ユーザー", + "operator-user-abbrev": "@", + "operator-member": "メンバー", + "operator-member-abbrev": "m", + "operator-assignee": "担当者", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "ステータス", + "operator-due": "期限日", + "operator-created": "作成日", + "operator-modified": "変更日", + "operator-sort": "並べ替え", + "operator-comment": "コメント", + "operator-has": "所有", + "operator-limit": "制限", + "predicate-archived": "アーカイブ済み", + "predicate-open": "open", + "predicate-ended": "終了", + "predicate-all": "すべて", + "predicate-overdue": "超過", + "predicate-week": "週", + "predicate-month": "月", + "predicate-quarter": "半年", + "predicate-year": "年", + "predicate-due": "期限日", + "predicate-modified": "変更日", + "predicate-created": "作成日", + "predicate-attachment": "添付", + "predicate-description": "詳細", + "predicate-checklist": "チェックリスト", + "predicate-start": "開始", + "predicate-end": "終了", + "predicate-assignee": "担当者", + "predicate-member": "メンバー", + "predicate-public": "公開", + "predicate-private": "非公開", + "operator-unknown-error": "%sは演算子ではありません", + "operator-number-expected": "演算子 __operator__ には数値の指定が必要ですが、「__value__」が入力されました", + "operator-sort-invalid": "ソート「%s」は無効です", + "operator-status-invalid": "「%s」は無効なステータスです", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s は有効な制限ではありません。制限は正の整数である必要があります。", + "next-page": "次のページ", + "previous-page": "前のページ", + "heading-notes": "注意\n ", + "globalSearch-instructions-heading": "検索手順", + "globalSearch-instructions-description": "検索は演算子を含めた絞り込みが可能です。演算子は、演算子の名前と値を半角コロンで区切った記述で指定します。例えば、`リスト:対応不可`と指定すると、*対応不可*という名前のリストに含まれるカードという制約で検索します。値にスペースや特殊文字が含まれる場合は引用符で囲む必要があります(例:`__operator_list__:\"レビュー 本日中\"`)。", + "globalSearch-instructions-operators": "有効な演算子:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - 指定された *<title>* に一致するボード内のカード", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - 指定された *<title>* に一致するリスト内のカード", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - 指定された *<title>* に一致するスイムレーン内のカード", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - *<text>* を含むコメントがあるカード", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - *<color>* または *<name>* に一致するラベルを持つカード", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - `__operator_label__:<color>` または `__operator_label__:<name>` の短縮表現", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - *<username>* が *メンバー* または *担当者* に割り当てられたカード", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - `ユーザー:<username>` の短縮表現", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - *<username>* が *メンバー* のカード", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - *<username>* が *担当者* のカード", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - *<username>* が作成者のカード", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - 今から *<n>* 日以内に期限が切れるカード `__operator_due__:__predicate_overdue__` - 期限が切れたすべてのカード", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - *<n>* 日以内に作成されたカード", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - *<n>* 日以内に変更されたカード", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - *<status>* が以下のうちのどれか:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - アーカイブされたカード", + "globalSearch-instructions-status-all": "`__predicate_all__` - すべてのアーカイブされた、アーカイブされていないカード", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - 終了日が記載されたカード", + "globalSearch-instructions-status-public": "`__predicate_public__` - 公開されたボードにあるカード", + "globalSearch-instructions-status-private": "`__predicate_private__` - 非公開のボードにあるカード", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - *<field>* は`__predicate_attachment__`、`__predicate_checklist__`、`__predicate_description__`、`__predicate_start__`、`__predicate_due__`、`__predicate_end__`、`__predicate_assignee__`、`__predicate_member__` のいずれかを指定します。*<field>* の前に「`-`」を記述すると、そのフィールドに値がないものを検索します(例:「`所有:-期限日`」は期限日がないカードを検索します)。", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - *<sort-name>* は `__predicate_due__`、`__predicate_created__`、`__predicate_modified__` のいずれかを指定します。降順にする場合は、並べ替え名の前に「`-`」を記述します。", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - 「*<n>*」には、ページごとに表示されるカードの数を表す正の整数を指定します。 ", + "globalSearch-instructions-notes-1": "複数の演算子を指定できます。", + "globalSearch-instructions-notes-2": "同じ演算子はOR条件になります。複数の条件のいずれかに一致したカードが検索されます。`__operator_list__:有効 __operator_list__:対応不要` の場合、リスト名が *有効* または *対応不要* のカードが検索されます。", + "globalSearch-instructions-notes-3": "異なる演算子はAND条件になります。異なる演算子すべてに一致したカードのみが検索されます。`__operator_list__:有効 __operator_label__:赤` は、*有効* のリストの中の *赤* のラベルのカードのみが検索されます。", + "globalSearch-instructions-notes-3-2": "日付には正負の整数や、現在からの期間として `__predicate_week__`、`__predicate_month__`、`__predicate_quarter__`、`__predicate_year__` を指定できます。", + "globalSearch-instructions-notes-4": "テキスト検索では大文字と小文字は区別されません。", + "globalSearch-instructions-notes-5": "デフォルトではアーカイブされたカードは検索されません。 ", + "link-to-search": "この検索へのリンク", + "excel-font": "Arial", + "number": "数値", + "label-colors": "ラベル色", + "label-names": "ラベル名", + "archived-at": "アーカイブされた:", + "sort-cards": "カードの並び替え", + "cardsSortPopup-title": "カードの並び替え", + "due-date": "期限日", + "server-error": "サーバーエラー", + "server-error-troubleshooting": "サーバーによって生成されたエラーを送信してください。\nsnapを用いてインストールされている場合、次のコマンドを実行します: `sudo snap logs wekan.wekan`\nDockerを用いてインストールされている場合、次のコマンドを実行します: `sudo docker logs wekan-app`", + "title-alphabetically": "タイトル(アルファベット順)", + "created-at-newest-first": "作成日(新しいものから)", + "created-at-oldest-first": "作成日(古いものから)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "全ユーザーのシステムメッセージを非表示にする", + "now-system-messages-of-all-users-are-hidden": "現在全ユーザーのシステムメッセージが非表示になっています", + "move-swimlane": "スイムレーンを移動する", + "moveSwimlanePopup-title": "スイムレーンを移動する", + "custom-field-stringtemplate": "文字列テンプレート", + "custom-field-stringtemplate-format": "書式(%{value}をプレースホルダーとして使用)", + "custom-field-stringtemplate-separator": "セパレーター( または   は空白として使用)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "作成者", + "filesReportTitle": "ファイルレポート", + "orphanedFilesReportTitle": "孤立ファイルレポート", + "reports": "レポート", + "rulesReportTitle": "ルールレポート", + "copy-swimlane": "スイムレーンをコピーする", + "copySwimlanePopup-title": "スイムレーンをコピーする", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ka.i18n.json b/i18n/ka.i18n.json index eedd98661..7a95c2047 100644 --- a/i18n/ka.i18n.json +++ b/i18n/ka.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "მიბმული ფაილის დამატება", "add-board": "დაფის დამატება", + "add-template": "Add Template", "add-card": "ბარათის დამატება", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "ბილიკის დამატება", "add-subtask": "ქვესაქმიანობის დამატება", "add-checklist": "კატალოგის დამატება", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "უფლებამოსილი წევრი", "attached": "მიბმული", "attachment": "მიბმული ფიალი", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "გსურთ მიბმული ფაილის წაშლა? ", "attachments": "მიბმული ფაილები", "auto-watch": "დაფის ავტომატური ნახვა მას შემდეგ რაც ის შეიქმნება", - "avatar-too-big": "დიდი მოცულობის სურათი (მაქსიმუმ 70KB)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "უკან", "board-change-color": "ფერის შეცვლა", "board-nb-stars": "%s ვარსკვლავი", "board-not-found": "დაფა არ მოიძებნა", "board-private-info": "ეს დაფა იქნება <strong>პირადი</strong>.", "board-public-info": "ეს დაფა იქნება <strong>საჯარო</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "დაფის ფონის ცვლილება", "boardChangeTitlePopup-title": "დაფის სახელის ცვლილება", "boardChangeVisibilityPopup-title": "ხილვადობის შეცვლა", @@ -138,6 +148,7 @@ "board-view-cal": "კალენდარი", "board-view-swimlanes": "ბილიკები", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "ჩამონათვალი", "bucket-example": "მაგალითად “Bucket List” ", "cancel": "გაუქმება", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "მიბმა შემდეგი წყაროდან: ", "cardCustomField-datePopup-title": "დროის ცვლილება", "cardCustomFieldsPopup-title": "მომხმარებლის ველის შესწორება", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "წავშალოთ ბარათი? ", "cardDetailsActionsPopup-title": "ბარათის მოქმედებები", "cardLabelsPopup-title": "ნიშნები", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "ბარათები", "cards-count": "ბარათები", + "cards-count-one": "Card", "casSignIn": "შესვლა CAS-ით", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "დახურვა", "close-board": "დაფის დახურვა", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "შავი", "color-blue": "ლურჯი", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "მიმდინარე", "custom-field-delete-pop": "ქმედება გამოიწვევს მომხმარებლის ველის წაშლას ყველა ბარათიდან და გაანადგურებს მის ისტორიას, რის შემდეგაც შეუძლებელი იქნება მისი უკან დაბრუნება. ", "custom-field-checkbox": "მოსანიშნი გრაფა", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "თარიღი", "custom-field-dropdown": "ჩამოსაშლელი სია", "custom-field-dropdown-none": "(ცარიელი)", @@ -297,13 +345,27 @@ "error-board-notAMember": "ამის გასაკეთებლად საჭიროა იყოთ დაფის წევრი", "error-json-malformed": "შენი ტექსტი არ არის ვალიდური JSON", "error-json-schema": "თქვენი JSON მონაცემები არ შეიცავს ზუსტ ინფორმაციას სწორ ფორმატში ", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "ეს ცხრილი არ არსებობს", "error-user-doesNotExist": "მსგავსი მომხმარებელი არ არსებობს", "error-user-notAllowSelf": "თქვენ არ შეგიძლიათ საკუთარი თავის მოწვევა", "error-user-notCreated": "მომხმარებელი არ შეიქმნა", "error-username-taken": "არსებობს მსგავსი მომხმარებელი", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "უკვე არსებობს მსგავსი ელ.ფოსტა", "export-board": "დაფის ექსპორტი", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "დაფის ექსპორტი", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "ფილტრი", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "ფილტრის გასუფთავება", + "filter-labels-label": "Filter by label", "filter-no-label": "ნიშანი არ გვაქვს", + "filter-member-label": "Filter by member", "filter-no-member": "არ არის წევრები ", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "არა მომხმარებლის ველი", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "ფილტრი ჩართულია", "filter-on-desc": "თქვენ ფილტრავთ ბარათებს ამ დაფაზე. დააკლიკეთ აქ ფილტრაციის შესწორებისთვის. ", "filter-to-selection": "მონიშნულის გაფილტვრა", + "other-filters-label": "Other Filters", "advanced-filter-label": "გაფართოებული ფილტრაცია", "advanced-filter-description": "გაფართოებული ფილტრაცია, უფლებას გაძლევთ დაწეროთ მწკრივი რომლებიც შეიცავენ შემდეგ ოპერაციებს : == != <= >= && || ( ) space გამოიყენება როგორც გამმიჯნავი ოპერაციებს შორის. თქვენ შეგიძლიათ გაფილტროთ მომხმარებლის ველი მათი სახელებისა და ღირებულებების მიხედვით. მაგალითად: Field1 == Value1. გაითვალისწინეთ რომ თუ ველი ან ღირებულება შეიცავს space-ს თქვენ დაგჭირდებათ მათი მოთავსება ერთ ციტატაში მაგ: 'Field 1' == 'Value 1'. ერთი კონტროლის სიმბოლოებისთვის (' \\/) გამოტოვება, შეგიძლიათ გამოიყენოთ \\. მაგ: Field1 == I\\'m. აგრეთვე თქვენ შეგიძლიათ შეურიოთ რამოდენიმე კომბინაცია. მაგალითად: F1 == V1 || F1 == V2. როგორც წესი ყველა ოპერაცია ინტერპრეტირებულია მარცხნიდან მარჯვნივ. თქვენ შეგიძლიათ შეცვალოთ რიგითობა ფრჩხილების შეცვლით მაგალითად: F1 == V1 && ( F2 == V2 || F2 == V3 ). აგრეთვე შეგიძლიათ მოძებნოთ ტექსტის ველები რეგექსით F1 == /Tes.*/i", "fullname": "სახელი და გვარი", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "დაფის შექმნა", "home": "სახლი", "import": "იმპორტირება", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": " დაფის იმპორტი", "import-board-c": "დაფის იმპორტი", "import-board-title-trello": "დაფის იმპორტი Trello-დან", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "იმპორტირებული დაფა წაშლის ყველა არსებულ მონაცემს დაფაზე და შეანაცვლებს მას იმპორტირებული დაფა. ", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Trello-დან", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "თქვენს Trello დაფაზე, შედით \"მენიუ\"-ში, შემდეგ დააკლიკეთ \"მეტი\", \"ამოპრინტერება და ექსპორტი\", \"JSON-ის ექსპორტი\" და დააკოპირეთ შედეგი. ", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "მოათავსეთ თქვენი ვალიდური JSON მონაცემები აქ. ", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "რუკის წევრები", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "მომხმარებლის რუკების განხილვა", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "მონიშნე ყველა ბარათი ამ სიაში", "set-color-list": "Set Color", "listActionPopup-title": "მოქმედებების სია", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "ბილიკის მოქმედებები", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Trello ბარათის იმპორტი", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "მეტი", "link-list": "დააკავშირეთ ამ ჩამონათვალთან", "list-delete-pop": "ყველა მოქმედება წაიშლება აქტივობების ველიდან და თქვენ ვეღარ შეძლებთ მის აღდგენას ჩამონათვალში", @@ -396,6 +478,8 @@ "moveCardToTop-title": "ზევით აწევა", "moveSelectionPopup-title": "მონიშნულის მოძრაობა", "multi-selection": "რამდენიმეს მონიშვნა", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "რამდენიმეს მონიშვნა ჩართულია", "muted": "ხმა გათიშულია", "muted-info": "თქვენ აღარ მიიღებთ შეტყობინებას ამ დაფაზე მიმდინარე ცვლილებების შესახებ. ", @@ -441,8 +525,9 @@ "search": "ძებნა", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "საძიებო ტექსტი", + "search-example": "Write text you search and press Enter", "select-color": "ფერის მონიშვნა", + "select-board": "Select Board", "set-wip-limit-value": "დააყენეთ შეზღუდვა დავალებების მაქსიმალურ რაოდენობაზე ", "setWipLimitPopup-title": "დააყენეთ WIP ლიმიტი", "shortcut-assign-self": "მონიშნეთ საკუთარი თავი აღნიშნულ ბარათზე", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "ჩემი ბარათების გაფილტვრა", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "ფილტრაციის გვერდითა ღილაკი", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "გვერდით მენიუს ჩართვა/გამორთვა", "show-cards-minimum-count": "აჩვენეთ ბარათების დათვლილი რაოდენობა თუ ჩამონათვალი შეიცავს უფრო მეტს ვიდრე ", "sidebar-open": "გახსენით მცირე სტატია", @@ -481,7 +567,15 @@ "upload": "ატვირთვა", "upload-avatar": "სურათის ატვირთვა", "uploaded-avatar": "სურათი ატვირთულია", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "მომხმარებლის სახელი", + "import-usernames": "Import Usernames", "view-it": "ნახვა", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "ნახვა", @@ -553,7 +647,8 @@ "minutes": "წუთები", "seconds": "წამები", "show-field-on-card": "აჩვენეთ ეს ველი ბარათზე", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "დიახ", "no": "არა", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "ელ.ფოსტის ცვლილების უფლების დაშვება", "accounts-allowUserNameChange": "მომხმარებლის სახელის ცვლილების უფლების დაშვება ", "createdAt": "შექმნილია", + "modifiedAt": "Modified at", "verified": "შემოწმებული", "active": "აქტიური", "card-received": "მიღებული", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "უფლებამოსილების გამცემი ", "requested-by": "მომთხოვნი", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "წაშლის შემთხვევაში თქვენ დაკარგავთ ამ დაფასთან ასოცირებულ ყველა მონაცემს მათ შორის : ჩამონათვალს, ბარათებს და მოქმედებებს. ", "delete-board-confirm-popup": "ყველა ჩამონათვალი, ბარათი, ნიშანი და აქტივობა წაიშლება და თქვენ ვეღარ შეძლებთ მის აღდგენას. ", "boardDeletePopup-title": "წავშალოთ დაფა? ", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "რიცხვი", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/km.i18n.json b/i18n/km.i18n.json index 167189663..4a4f61afb 100644 --- a/i18n/km.i18n.json +++ b/i18n/km.i18n.json @@ -1,6 +1,6 @@ { "accept": "យល់ព្រម", - "act-activity-notify": "Activity Notification", + "act-activity-notify": "ដំណឹងនៃសកម្មភាព", "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "តម្រងកាតរបស់ខ្ញុំ", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ko.i18n.json b/i18n/ko.i18n.json index ccb9dfbd1..5ae09284d 100644 --- a/i18n/ko.i18n.json +++ b/i18n/ko.i18n.json @@ -49,7 +49,7 @@ "activity-archived": "%s moved to Archive", "activity-attached": "%s를 %s에 첨부함", "activity-created": "%s 생성됨", - "activity-customfield-created": "created custom field %s", + "activity-customfield-created": "생성된 사용자 정의 필드 %s", "activity-excluded": "%s를 %s에서 제외함", "activity-imported": "imported %s into %s from %s", "activity-imported-board": "imported %s from %s", @@ -73,11 +73,18 @@ "activity-unchecked-item-card": "unchecked %s in checklist %s", "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-editComment": "수정된 댓글", + "activity-deleteComment": "삭제된 댓글", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "첨부파일 추가", "add-board": "보드 추가", + "add-template": "Add Template", "add-card": "카드 추가", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "체크리스트 추가", @@ -90,7 +97,7 @@ "addMemberPopup-title": "멤버", "admin": "관리자", "admin-desc": "카드를 보거나 수정하고, 멤버를 삭제하고, 보드에 대한 설정을 수정할 수 있습니다.", - "admin-announcement": "Announcement", + "admin-announcement": "공지사항", "admin-announcement-active": "시스템에 공지사항을 표시합니다", "admin-announcement-title": "관리자 공지사항 메시지", "all-boards": "전체 보드", @@ -98,7 +105,7 @@ "and-n-other-card_plural": "__count__ 개의 다른 카드들", "apply": "적용", "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", + "archive": "보관으로 이동", "archive-all": "Move All to Archive", "archive-board": "Move Board to Archive", "archive-card": "Move Card to Archive", @@ -107,12 +114,14 @@ "archive-selection": "Move selection to Archive", "archiveBoardPopup-title": "Move Board to Archive?", "archived-items": "보관", - "archived-boards": "Boards in Archive", + "archived-boards": "보관 중인 보드", "restore-board": "보드 복구", - "no-archived-boards": "No Boards in Archive.", + "no-archived-boards": "보관 중인 보드 없음.", "archives": "보관", - "template": "Template", + "template": "템플릿", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "멤버 지정", "attached": "첨부됨", "attachment": "첨부 파일", @@ -120,34 +129,36 @@ "attachmentDeletePopup-title": "첨부 파일을 삭제합니까?", "attachments": "첨부 파일", "auto-watch": "생성한 보드를 자동으로 감시합니다.", - "avatar-too-big": "아바타 파일이 너무 큽니다. (최대 70KB)", + "avatar-too-big": "아바타가 너무 큽니다. (최대 520KB)", "back": "뒤로", "board-change-color": "보드 색 변경", "board-nb-stars": "%s개의 별", "board-not-found": "보드를 찾을 수 없습니다", "board-private-info": "이 보드는 <strong>비공개</strong>입니다.", "board-public-info": "이 보드는 <strong>공개</strong>로 설정됩니다", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "보드 배경 변경", "boardChangeTitlePopup-title": "보드 이름 바꾸기", "boardChangeVisibilityPopup-title": "표시 여부 변경", "boardChangeWatchPopup-title": "감시상태 변경", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardMenuPopup-title": "보드 메뉴", + "boardChangeViewPopup-title": "보드 화면", "boards": "보드", - "board-view": "Board View", - "board-view-cal": "Calendar", + "board-view": "보드 화면", + "board-view-cal": "달력", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "간트", "board-view-lists": "목록들", "bucket-example": "예: “프로젝트 이름“ 입력", "cancel": "취소", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "이 카드는 보관함으로 이동 되었습니다.", + "board-archived": "이 보드는 보관함으로 이동 되었습니다.", "card-comments-title": "이 카드에 %s 코멘트가 있습니다.", "card-delete-notice": "영구 삭제입니다. 이 카드와 관련된 모든 작업들을 잃게됩니다.", "card-delete-pop": "모든 작업이 활동 내역에서 제거되며 카드를 다시 열 수 없습니다. 복구가 안되니 주의하시기 바랍니다.", "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "종료일", + "card-due": "마감기한", "card-due-on": "종료일", "card-spent": "Spent Time", "card-edit-attachments": "첨부 파일 수정", @@ -159,18 +170,52 @@ "card-start": "시작일", "card-start-on": "시작일", "cardAttachmentsPopup-title": "첨부 파일", - "cardCustomField-datePopup-title": "Change date", + "cardCustomField-datePopup-title": "날짜 변경", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "투표 시작", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "투표 수정", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "투표 질문", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "투표를 삭제합니까?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "카드를 삭제합니까?", "cardDetailsActionsPopup-title": "카드 액션", "cardLabelsPopup-title": "라벨", "cardMembersPopup-title": "멤버", "cardMorePopup-title": "더보기", - "cardTemplatePopup-title": "Create template", + "cardTemplatePopup-title": "템플릿 생성", "cards": "카드", "cards-count": "카드", + "cards-count-one": "카드", "casSignIn": "Sign In with CAS", - "cardType-card": "Card", + "cardType-card": "카드", "cardType-linkedCard": "Linked Card", "cardType-linkedBoard": "Linked Board", "change": "변경", @@ -191,43 +236,44 @@ "close": "닫기", "close-board": "보드 닫기", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "블랙", "color-blue": "블루", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "진홍색", + "color-darkgreen": "암녹색", + "color-gold": "금색", + "color-gray": "회색", "color-green": "그린", - "color-indigo": "indigo", + "color-indigo": "인디고", "color-lime": "라임", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-magenta": "자홍색", + "color-mistyrose": "미스티로즈", + "color-navy": "남색", "color-orange": "오렌지", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "청록색", + "color-peachpuff": "피치퍼프", "color-pink": "핑크", - "color-plum": "plum", + "color-plum": "진자주색", "color-purple": "퍼플", "color-red": "레드", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "새들브라운", + "color-silver": "은", "color-sky": "스카이", - "color-slateblue": "slateblue", - "color-white": "white", + "color-slateblue": "슬레이트블루", + "color-white": "흰색", "color-yellow": "옐로우", "unset-color": "Unset", "comment": "댓글", "comment-placeholder": "댓글 입력", "comment-only": "댓글만 입력 가능", "comment-only-desc": "카드에 댓글만 달수 있습니다.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", + "no-comments": "댓글 없음", + "no-comments-desc": "댓글과 활동내역을 볼 수 없습니다.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "내 컴퓨터", "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "confirm-checklist-delete-dialog": "정말로 체크리스트를 삭제하실 건가요?", "copy-card-link-to-clipboard": "클립보드에 카드의 링크가 복사되었습니다.", "linkCardPopup-title": "Link Card", "searchElementPopup-title": "검색", @@ -239,19 +285,21 @@ "createBoardPopup-title": "보드 생성", "chooseBoardSourcePopup-title": "보드 가져오기", "createLabelPopup-title": "라벨 생성", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", + "createCustomField": "필드 생성", + "createCustomFieldPopup-title": "필드 생성", "current": "경향", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", + "custom-field-checkbox": "체크 박스", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "날짜", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", "custom-field-dropdown-options": "List Options", "custom-field-dropdown-options-placeholder": "Press enter to add more options", "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", + "custom-field-number": "숫자", + "custom-field-text": "텍스트", "custom-fields": "Custom Fields", "date": "날짜", "decline": "쇠퇴", @@ -272,7 +320,7 @@ "soft-wip-limit": "원만한 WIP 제한", "editCardStartDatePopup-title": "시작일 변경", "editCardDueDatePopup-title": "종료일 변경", - "editCustomFieldPopup-title": "Edit Field", + "editCustomFieldPopup-title": "필드 수정", "editCardSpentTimePopup-title": "Change spent time", "editLabelPopup-title": "라벨 변경", "editNotificationPopup-title": "알림 수정", @@ -297,34 +345,60 @@ "error-board-notAMember": "이 작업은 보드의 멤버만 실행할 수 있습니다.", "error-json-malformed": "텍스트가 JSON 형식에 유효하지 않습니다.", "error-json-schema": "JSON 데이터에 정보가 올바른 형식으로 포함되어 있지 않습니다.", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "목록이 없습니다.", "error-user-doesNotExist": "멤버의 정보가 없습니다.", "error-user-notAllowSelf": "자기 자신을 초대할 수 없습니다.", "error-user-notCreated": "유저가 생성되지 않았습니다.", "error-username-taken": "중복된 아이디 입니다.", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "보드 내보내기", - "sort": "Sort", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "보드 내보내기", + "exportCardPopup-title": "Export card", + "sort": "분류", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", + "list-label-title": "리스트 이름", "list-label-sort": "Your Manual Order", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "필터", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "필터 초기화", + "filter-labels-label": "Filter by label", "filter-no-label": "라벨 없음", + "filter-member-label": "Filter by member", "filter-no-member": "멤버 없음", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "필터 사용", "filter-on-desc": "보드에서 카드를 필터링합니다. 여기를 클릭하여 필터를 수정합니다.", "filter-to-selection": "선택 항목으로 필터링", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "실명", @@ -333,24 +407,28 @@ "headerBarCreateBoardPopup-title": "보드 생성", "home": "홈", "import": "가져오기", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "보드 가져오기", "import-board-c": "보드 가져오기", "import-board-title-trello": "Trello에서 보드 가져오기", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "Trello 게시판에서 'Menu' -> 'More' -> 'Print and Export', 'Export JSON' 선택하여 텍스트 결과값 복사", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "유효한 JSON 데이터를 여기에 붙여 넣으십시오.", + "import-csv-placeholder": "유효한 CSV/TSV 정보를 붙여 넣으십시오.", "import-map-members": "보드 멤버들", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "멤버 매핑 미리보기", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", + "import-user-select": "이 멤버로 사용할 기존 사용자를 선택하십시오.", + "importMapMembersAddPopup-title": "멤버 선택", "info": "Version", "initials": "이니셜", "invalid-date": "적절하지 않은 날짜", @@ -373,11 +451,15 @@ "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", "list-move-cards": "목록에 있는 모든 카드를 이동", "list-select-cards": "목록에 있는 모든 카드를 선택", - "set-color-list": "Set Color", + "set-color-list": "색 설정", "listActionPopup-title": "동작 목록", + "settingsUserPopup-title": "사용자 설정", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Trello 카드 가져 오기", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "더보기", "link-list": "이 리스트에 링크", "list-delete-pop": "모든 작업이 활동내역에서 제거되며 리스트를 복구 할 수 없습니다. 실행 취소는 불가능 합니다.", @@ -396,13 +478,15 @@ "moveCardToTop-title": "최상단으로 이동", "moveSelectionPopup-title": "선택 항목 이동", "multi-selection": "다중 선택", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "다중 선택 사용", "muted": "알림 해제", "muted-info": "보드의 변경된 사항들의 알림을 받지 않습니다.", "my-boards": "내 보드", "name": "이름", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", + "no-archived-cards": "보관 중인 카드 없음.", + "no-archived-lists": "보관중인 목록 없음.", "no-archived-swimlanes": "No swimlanes in Archive.", "no-results": "결과 값 없음", "normal": "표준", @@ -439,10 +523,11 @@ "restore": "복구", "save": "저장", "search": "검색", - "rules": "Rules", + "rules": "규칙", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "색 선택", + "select-board": "보드 선택", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "현재 카드에 자신을 지정하세요.", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "내 카드 필터링", "shortcut-show-shortcuts": "바로가기 목록을 가져오십시오.", "shortcut-toggle-filterbar": "토글 필터 사이드바", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "보드 사이드바 토글", "show-cards-minimum-count": "목록에 카드 수량 표시(입력된 수량 넘을 경우 표시)", "sidebar-open": "사이드바 열기", @@ -474,14 +560,22 @@ "title": "제목", "tracking": "추적", "tracking-info": "보드 생성자 또는 멤버로 참여하는 모든 카드에 대한 변경사항 알림 받음", - "type": "Type", + "type": "타입", "unassign-member": "멤버 할당 해제", "unsaved-description": "저장되지 않은 설명이 있습니다.", "unwatch": "감시 해제", "upload": "업로드", "upload-avatar": "아바타 업로드", "uploaded-avatar": "업로드한 아바타", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "아이디", + "import-usernames": "Import Usernames", "view-it": "보기", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "감시", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,31 +656,33 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", "card-received-on": "Received on", - "card-end": "End", + "card-end": "종료일", "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", + "setCardActionsColorPopup-title": "색상 선택", + "setSwimlaneColorPopup-title": "색상 선택", + "setListColorPopup-title": "색상 선택", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", + "delete-board-confirm-popup": "모든 목록, 카드, 레이블 및 활동이 삭제되고 보드 내용을 복구할 수 없습니다. 실행 취소는 불가능합니다.", + "boardDeletePopup-title": "보드 삭제?", + "delete-board": "보드 삭제", "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", + "default": "기본", "queue": "Queue", "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", + "card-settings": "카드 설정", "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", + "boardCardSettingsPopup-title": "카드 설정", "deposit-subtasks-board": "Deposit subtasks to this board:", "deposit-subtasks-list": "Landing list for subtasks deposited here:", "show-parent-in-minicard": "Show parent in minicard:", @@ -600,100 +697,104 @@ "activity-added-label": "added label '%s' to %s", "activity-removed-label": "removed label '%s' from %s", "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", + "activity-added-label-card": "라벨 '%s' 추가", + "activity-removed-label-card": "라벨 '%s' 제거", + "activity-delete-attach-card": "첨부 파일 제거", "activity-set-customfield": "set custom field '%s' to '%s' in %s", "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", + "r-rule": "규칙", "r-add-trigger": "Add trigger", - "r-add-action": "Add action", + "r-add-action": "동작 추가", "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", + "r-add-rule": "규칙 추가", + "r-view-rule": "규칙 보기", + "r-delete-rule": "규칙 삭제", + "r-new-rule-name": "새로운 규칙 제목", + "r-no-rules": "규칙 없음", + "r-trigger": "Trigger", + "r-action": "동작", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", - "r-list": "list", + "r-list": "목록", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", "r-archived": "Moved to Archive", "r-unarchived": "Restored from Archive", - "r-a-card": "a card", + "r-a-card": "카드", "r-when-a-label-is": "When a label is", "r-when-the-label": "When the label", - "r-list-name": "list name", + "r-list-name": "목록 이름", "r-when-a-member": "When a member is", "r-when-the-member": "When the member", - "r-name": "name", + "r-name": "이름", "r-when-a-attach": "When an attachment", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", + "r-completed": "완료", "r-made-incomplete": "Made incomplete", "r-when-a-item": "When a checklist item is", "r-when-the-item": "When the checklist item", - "r-checked": "Checked", + "r-checked": "선택", "r-unchecked": "Unchecked", "r-move-card-to": "Move card to", "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-archive": "Move to Archive", + "r-archive": "보관으로 이동", "r-unarchive": "Restore from Archive", - "r-card": "card", + "r-card": "카드", "r-add": "추가", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", + "r-remove": "제거", + "r-label": "라벨", + "r-member": "멤버", "r-remove-all": "Remove all members from the card", "r-set-color": "Set color to", "r-checklist": "checklist", - "r-check-all": "Check all", + "r-check-all": "전체 선택", "r-uncheck-all": "Uncheck all", "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", + "r-check": "선택", + "r-uncheck": "선택되지 않은 항목", + "r-item": "항목", "r-of-checklist": "of checklist", - "r-send-email": "Send an email", + "r-send-email": "이메일 전송", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-top-spec": "카드를 목록의 맨 위로 이동", "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", + "r-d-move-to-bottom-spec": "카드를 목록의 맨 아래로 이동", + "r-d-send-email": "이메일 전송", "r-d-send-email-to": "to", "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", + "r-d-send-email-message": "메시지", "r-d-archive": "Move card to Archive", "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", + "r-d-add-label": "라벨 추가", + "r-d-remove-label": "라벨 삭제", + "r-create-card": "새 카드 생성", "r-in-list": "목록에", "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", + "r-d-add-member": "멤버 추가", + "r-d-remove-member": "멤버 제거", + "r-d-remove-all-member": "모든 멤버 제거", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", + "r-d-check-one": "항목 선택", "r-d-uncheck-one": "Uncheck item", "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", + "r-d-add-checklist": "체크리스트 추가", + "r-d-remove-checklist": "체크리스트 삭제", "r-by": "by", - "r-add-checklist": "Add checklist", + "r-add-checklist": "체크리스트 추가", "r-with-items": "with items", "r-items-list": "item1,item2,item3", "r-add-swimlane": "Add swimlane", @@ -702,21 +803,21 @@ "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "When a card is moved to another list", "r-set": "Set", - "r-update": "Update", + "r-update": "업데이트", "r-datefield": "date field", - "r-df-start-at": "start", + "r-df-start-at": "시작", "r-df-due-at": "due", - "r-df-end-at": "end", + "r-df-end-at": "종료", "r-df-received-at": "received", "r-to-current-datetime": "to current date/time", "r-remove-value-from": "Remove value from", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", + "authentication-method": "인증 방법", + "authentication-type": "인증 유형", "custom-product-name": "Custom Product Name", - "layout": "Layout", + "layout": "레이아웃", "hide-logo": "Hide Logo", "add-custom-html-after-body-start": "Add Custom HTML after <body> start", "add-custom-html-before-body-end": "Add Custom HTML before </body> end", @@ -724,13 +825,15 @@ "error-ldap-login": "An error occurred while trying to login", "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", + "duplicate-board": "보드 복사", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", "restore-all": "모든항목 복구", "delete-all": "모두 삭제", - "loading": "Loading, please wait.", + "loading": "로딩중, 기다려주십시오.", "previous_as": "last time was", "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", @@ -750,20 +853,210 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", "assignee": "Assignee", "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", + "addmore-detail": "자세한 설명 추가", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "알림", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "월요일", + "tuesday": "화요일", + "wednesday": "수요일", + "thursday": "목요일", + "friday": "금요일", + "saturday": "토요일", + "sunday": "일요일", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "마지막 활동", + "voting": "투표", + "archived": "보관된", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "선택된 항목 숨기기", + "task": "과제", + "create-task": "과제 생성", + "ok": "확인", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "카드", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "목록", + "operator-list-abbrev": "l", + "operator-label": "라벨", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "멤버", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "시작", + "predicate-end": "종료", + "predicate-assignee": "assignee", + "predicate-member": "멤버", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "숫자", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/lt.i18n.json b/i18n/lt.i18n.json new file mode 100644 index 000000000..f596968c0 --- /dev/null +++ b/i18n/lt.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Accept", + "act-activity-notify": "Activity Notification", + "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "created board __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Actions", + "activities": "Activities", + "activity": "Activity", + "activity-added": "added %s to %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/lv.i18n.json b/i18n/lv.i18n.json index 10755775c..b8e3cbeb5 100644 --- a/i18n/lv.i18n.json +++ b/i18n/lv.i18n.json @@ -1,769 +1,1062 @@ { "accept": "Piekrist", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-activity-notify": "Darbību paziņojumi", + "act-addAttachment": "pievienoja failu __attachment__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-deleteAttachment": "dzēsa failu __attachment__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addSubtask": "pievienoja apakšuzdevumu __subtask__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addLabel": "Pievienoja birku __label__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addedLabel": "Pievienoja birku __label__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-removeLabel": "Dzēsa birku __label__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-removedLabel": "Dzēsa birku __label__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addChecklist": "pievienoja kontrolsarakstu __checklist__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addChecklistItem": "pievienoja kontrolsaraksta elementu __checklistItem__ sarakstam _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-removeChecklist": "dzēsa kontrolsarakstu __checklist__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-removeChecklistItem": "dzēsa kontrolsaraksta elementu __checklistItem__ sarakstam _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-checkedItem": "atzīmēja elementu __checklistItem__ kontrolsarakstā _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-uncheckedItem": "izņēma atzīmi elementam __checklistItem__ kontrolsarakstā _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-completeChecklist": "pabeidza kontrolsarakstu _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-uncompleteChecklist": "atsāka kontrolsarakstu _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-addComment": "komentēja kartiņu __card__: __comment__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-editComment": "laboja komentāru kartiņai __card__: __comment__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-deleteComment": "dzēsa komentāru kartiņai __card__: __comment__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-createBoard": "izveidoja dēli __board__", + "act-createSwimlane": "izveidoja joslu __swimlane__ dēlim __board__", + "act-createCard": "izveidoja kartiņu __card__ sarakstā __list__ joslai __swimlane__ dēlī __board__", + "act-createCustomField": "pievienoja lauku __customField__ dēlī __board__", + "act-deleteCustomField": "dzēsa lauku __customField__ dēlī __board__", + "act-setCustomField": "laboja lauku __customField__: __customFieldValue__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-createList": "pievienoja sarakstu __list__ dēlim __board__", + "act-addBoardMember": "pievienoja dalībnieku __member__ dēlim __board__", + "act-archivedBoard": "Dēlis __board__ pārvietots uz arhīvu", + "act-archivedCard": "Kartiņa __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__ pārvietota uz arhīvu", + "act-archivedList": "Saraksts __list__ joslā __swimlane__ dēlī __board__ pārvietots uz arhīvu", + "act-archivedSwimlane": "Josla __swimlane__ dēlī __board__ pārvietota uz arhīvu", + "act-importBoard": "importēja dēli __board__", + "act-importCard": "importēja kartiņu __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-importList": "importēja sarakstu __list__ joslā __swimlane__ dēlī __board__", + "act-joinMember": "pievienoja dalībnieku __member__ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-moveCard": "pārvietoja kartiņu __card__ dēlī __board__ no saraksta __oldList__ joslā __oldSwimlane__ uz sarakstu __list__ joslā __swimlane__", + "act-moveCardToOtherBoard": "pārvietoja kartiņu __card__ no saraksta __oldList__ joslā __oldSwimlane__ dēlī __oldBoard__ uz sarakstu __list__ joslā __swimlane__ dēlī __board__", + "act-removeBoardMember": "noņēma dalībnieku __member__ no dēļa __board__", + "act-restoredCard": "atjaunoja kartiņu __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "act-unjoinMember": "noņēma dalībnieku __member__ no kartiņas __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "Darbības", - "activities": "Aktivitātes", - "activity": "Aktivitāte", + "activities": "Darbības", + "activity": "Darbība", "activity-added": "pievienoja %s pie %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "%s pārvietoja uz arhīvu", "activity-attached": "pievienoja %s pie %s", "activity-created": "izveidoja%s", - "activity-customfield-created": "created custom field %s", + "activity-customfield-created": "izveidoja lauku %s", "activity-excluded": "izslēdza%s no%s", - "activity-imported": "importēja %s iekšā%s no%s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-card": "Add Card", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "add-cover": "Add Cover", - "add-label": "Add Label", - "add-list": "Add List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Members", - "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", - "back": "Back", - "board-change-color": "Change color", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-lists": "Lists", - "bucket-example": "Like “Bucket List” for example", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardDeletePopup-title": "Delete Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", + "activity-imported": "importēja %s iekš %s no %s", + "activity-imported-board": "importēja %s no %s", + "activity-joined": "pievienojās %s", + "activity-moved": "pārvietoja %s no %s uz %s", + "activity-on": "uz %s", + "activity-removed": "noņēma %s no %s", + "activity-sent": "nosūtīja %s uz %s", + "activity-unjoined": "atvienoja %s", + "activity-subtask-added": "pievienoja apakšuzdevumu %s", + "activity-checked-item": "atzīmēja %s kontrolsarakstā %s no %s", + "activity-unchecked-item": "izņēma atzīmi %s kontrolsarakstā %s no %s", + "activity-checklist-added": "pievienoja kontrolsarakstu %s", + "activity-checklist-removed": "noņēma kontrolsarakstu no %s", + "activity-checklist-completed": "pabeidza kontrolsarakstu %s no %s", + "activity-checklist-uncompleted": "atsāka kontrolsarakstu %s no %s", + "activity-checklist-item-added": "pievienoja kontrolsaraksta elementu no '%s' iekš %s", + "activity-checklist-item-removed": "noņēma saraksta elementu no '%s' iekš %s", + "add": "Pievienot", + "activity-checked-item-card": "atzīmēja %s kontrolsarakstā %s", + "activity-unchecked-item-card": "izņēma atzīmi %s kontrolsarakstā %s", + "activity-checklist-completed-card": "pabeidza kontrolsarakstu _checklist_ kartiņai __card__ sarakstā __list__ joslā __swimlane__ dēlī __board__", + "activity-checklist-uncompleted-card": "atsāka kontrolsarakstu %s", + "activity-editComment": "laboja komentāru %s", + "activity-deleteComment": "dzēsa komentāru %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Pievienot failu", + "add-board": "Pievienot dēli", + "add-template": "Add Template", + "add-card": "Pievienot kartiņu", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Pievienot joslu", + "add-subtask": "Pievienot apakšuzdevumu", + "add-checklist": "Pievienot kontrolsarakstu", + "add-checklist-item": "Pievienot elementu", + "add-cover": "Pievienot vāku", + "add-label": "Pievienot birku", + "add-list": "Pievienot sarakstu", + "add-members": "Pievienot dalībniekus", + "added": "Pievienots", + "addMemberPopup-title": "Dalībnieki", + "admin": "Administrators", + "admin-desc": "Var skatīt un labot kartiņas, noņemt dalībniekus un mainīt dēļa iestatījumus", + "admin-announcement": "Paziņojums", + "admin-announcement-active": "Aktīvizēts paziņojums visā sistēmā", + "admin-announcement-title": "Paziņojums no administratora", + "all-boards": "Visi dēļi", + "and-n-other-card": "Un __count__ cita kartiņa", + "and-n-other-card_plural": "Un __count__ citas kartiņas", + "apply": "Saglabāt", + "app-is-offline": "Notiek ielāde, lūdzu uzgaidiet. Lapas pārlāde izraisīs datu zudumu. Ja neizdodas ielāde, lūdzu pārbaudiet, vai serveris nav apstājies.", + "archive": "Pārvietot uz arhīvu", + "archive-all": "Pārvietot visu uz arhīvu", + "archive-board": "Pārvietot dēli uz arhīvu", + "archive-card": "Pārvietot kartiņu uz arhīvu", + "archive-list": "Pārvietot sarakstu uz arhīvu", + "archive-swimlane": "Pārvietot joslu uz arhīvu", + "archive-selection": "Pārvietot atzīmēto uz arhīvu", + "archiveBoardPopup-title": "Pārvietot dēli uz arhīvu?", + "archived-items": "Arhīvs", + "archived-boards": "Dēļi arhīvā", + "restore-board": "Atjaunot dēli", + "no-archived-boards": "Arhīvā nav dēļu.", + "archives": "Arhīvs", + "template": "Sagatave", + "templates": "Sagataves", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Iecelt dalībnieku", + "attached": "pievienots fails", + "attachment": "Fails", + "attachment-delete-pop": "Faila dzēšana ir neatgriezeniska. Darbību nevar atsaukt.", + "attachmentDeletePopup-title": "Dzēst failu?", + "attachments": "Faili", + "auto-watch": "Automātiski sekot dēļiem, kad tie tiek izveidoti", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Atpakaļ", + "board-change-color": "Mainīt krāsu", + "board-nb-stars": "%s zvaigznes", + "board-not-found": "Dēlis nav atrasts", + "board-private-info": "Šis dēlis būs <strong>privāts</strong>.", + "board-public-info": "Šis dēlis būs <strong>publisks</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Mainīt dēļa fonu", + "boardChangeTitlePopup-title": "Pārsaukt dēli", + "boardChangeVisibilityPopup-title": "Mainīt redzamību", + "boardChangeWatchPopup-title": "Mainīt sekošanu", + "boardMenuPopup-title": "Dēļa iestatījumi", + "boardChangeViewPopup-title": "Dēļa skats", + "boards": "Dēļi", + "board-view": "Dēļa skats", + "board-view-cal": "Kalendārs", + "board-view-swimlanes": "Joslas", + "board-view-collapse": "Sakļaut", + "board-view-gantt": "Gantt", + "board-view-lists": "Saraksti", + "bucket-example": "Piemēram \"Izdarāmie darbi\"", + "cancel": "Atcelt", + "card-archived": "Šī kartiņa ir pārvietota uz arhīvu", + "board-archived": "Šis dēlis ir pārvietots uz arhīvu", + "card-comments-title": "Šai kartiņai ir %s komentārs.", + "card-delete-notice": "Dzēšana ir neatgriezeniska. Jūs zaudēsiet visas ar kartiņu saistītās darbības.", + "card-delete-pop": "Visas darbības no saraksta tiks noņemtas un jūs vairs nevarēsiet kartiņu atvērt. Darbību nevar atsaukt.", + "card-delete-suggest-archive": "Lai kartiņu noņemtu no dēļa nezaudējot darbības, arhivējiet to.", + "card-due": "Termiņš", + "card-due-on": "Termiņa beigas", + "card-spent": "Patērētais laiks", + "card-edit-attachments": "Labot failus", + "card-edit-custom-fields": "Labot laukus", + "card-edit-labels": "Labot birkas", + "card-edit-members": "Labot dalībniekus", + "card-labels-title": "Mainīt kartiņas birkas.", + "card-members-title": "Pievienot vai noņemt dēļa dalībniekus kartiņai.", + "card-start": "Sākt", + "card-start-on": "Sākas", + "cardAttachmentsPopup-title": "Pievienot no", + "cardCustomField-datePopup-title": "Mainīt datumu", + "cardCustomFieldsPopup-title": "Labot laukus", + "cardStartVotingPopup-title": "Sākt balsojumu", + "positiveVoteMembersPopup-title": "Atbalstītāji", + "negativeVoteMembersPopup-title": "Oponenti", + "card-edit-voting": "Balsošana", + "editVoteEndDatePopup-title": "Labot balsojuma beigu datumu", + "allowNonBoardMembers": "Tikai pierakstītajiem lietotājiem", + "vote-question": "Balsojuma jautājums", + "vote-public": "Rādīt par ko katrs balsoja", + "vote-for-it": "priekš tā", + "vote-against": "pret", + "deleteVotePopup-title": "Dzēst balsojumu?", + "vote-delete-pop": "Dzēšana ir neatgriezeniska. Jūs zaudēsiet visas darbības saistītas ar šo balsojumu.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Dzēst kartiņu?", + "cardDetailsActionsPopup-title": "Kartiņas darbības", + "cardLabelsPopup-title": "Birkas", + "cardMembersPopup-title": "Dalībnieki", + "cardMorePopup-title": "Vairāk", + "cardTemplatePopup-title": "Izveidot sagatavi", + "cards": "Kartiņas", + "cards-count": "Kartiņas", + "cards-count-one": "Kartiņa", + "casSignIn": "Pierakstīties ar CAS", + "cardType-card": "Kartiņa", + "cardType-linkedCard": "Saistīta kartiņa", + "cardType-linkedBoard": "Saistīts dēlis", + "change": "Mainīt", + "change-avatar": "Mainīt attēlu", + "change-password": "Mainīt paroli", + "change-permissions": "Mainīt atļaujas", + "change-settings": "Iestatījumi", + "changeAvatarPopup-title": "Mainīt attēlu", + "changeLanguagePopup-title": "Mainīt valodu", + "changePasswordPopup-title": "Mainīt paroli", + "changePermissionsPopup-title": "Mainīt atļaujas", + "changeSettingsPopup-title": "Iestatījumi", + "subtasks": "Apakšuzdevumi", + "checklists": "Kontrolsaraksti", + "click-to-star": "Spied lai atzīmēt ar zvaigzni.", + "click-to-unstar": "Spied lai noņemtu zvaigzni.", + "clipboard": "Starpliktuve vai drag & drop", + "close": "Aizvērt", + "close-board": "Aizvērt dēli", + "close-board-pop": "Jūs varēsiet atjaunot dēli spiežot uz \"Arhīvs\" sākumā.", + "close-card": "Close Card", + "color-black": "melns", + "color-blue": "zils", + "color-crimson": "tumši sarkans", + "color-darkgreen": "tumši zaļš", + "color-gold": "zeltīts", + "color-gray": "pelēks", + "color-green": "zaļš", "color-indigo": "indigo", - "color-lime": "lime", + "color-lime": "laims", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", - "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", - "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "color-mistyrose": "viegli rozā", + "color-navy": "jūras zils", + "color-orange": "oranžš", + "color-paleturquoise": "blāvs tirkīzs", + "color-peachpuff": "persiks", + "color-pink": "rozā", + "color-plum": "plūme", + "color-purple": "purpurs", + "color-red": "sarkans", + "color-saddlebrown": "seglu brūns", + "color-silver": "sudrabots", + "color-sky": "debesu", + "color-slateblue": "šīfera zils", + "color-white": "balts", + "color-yellow": "dzeltens", + "unset-color": "Atiestatīt", + "comment": "Komentēt", + "comment-placeholder": "Rakstīt komentāru", + "comment-only": "Tikai komentēt", + "comment-only-desc": "Var komentēt tikai kartiņā.", + "no-comments": "Nav komentāru", + "no-comments-desc": "Nevar redzēt komentārus un darbības.", + "worker": "Darbonis", + "worker-desc": "Var tikai pārvietot kartiņas, pievienot sevi kartiņai un komentēt.", + "computer": "Dators", + "confirm-subtask-delete-dialog": "Vai tiešām vēlaties dzēst apakšuzdevumu?", + "confirm-checklist-delete-dialog": "Vai tiešam vēlaties dzēst kontrolsarakstu?", + "copy-card-link-to-clipboard": "Kopet kartiņas saiti starpliktuvē", + "linkCardPopup-title": "Kartiņas saite", + "searchElementPopup-title": "Meklēt", + "copyCardPopup-title": "Kopēt kartiņu", + "copyChecklistToManyCardsPopup-title": "Kopēt kontrolsarakstu uz citām kartiņām", + "copyChecklistToManyCardsPopup-instructions": "Mērķa kartiņu virsraksti un apraksti šajā JSON formātā", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Pirmās kartiņas virsraksts\", \"description\":\"Pirmās kartiņas apraksts}, {\"title\":\"Otrās kartiņas virsraksts\",\"description\":\"Otrās kartiņas apraksts\"},{\"title\":\"Pēdējās kartiņas virsraksts\",\"description\":\"Pēdējās kartiņas apraksts\"} ]", + "create": "Izveidot", + "createBoardPopup-title": "Izveidot dēli", + "chooseBoardSourcePopup-title": "Importēt dēli", + "createLabelPopup-title": "Izveidot birku", + "createCustomField": "Izveidot lauku", + "createCustomFieldPopup-title": "Izveidot lauku", + "current": "šobrīdējs", + "custom-field-delete-pop": "Darbību nevar atsaukt. Ar šo lauks tiks dzēsts no visām kartiņām un tā vēsture tiks iznīcināta.", + "custom-field-checkbox": "Kastīte", + "custom-field-currency": "Valūta", + "custom-field-currency-option": "Valūtas kods", + "custom-field-date": "Datums", + "custom-field-dropdown": "Izkrītošā izvēlne", + "custom-field-dropdown-none": "(nav)", + "custom-field-dropdown-options": "Saraksta opcijas", + "custom-field-dropdown-options-placeholder": "Spiediet enter lai pievienotu opcijas", + "custom-field-dropdown-unknown": "(nav zināms)", + "custom-field-number": "Skaits", + "custom-field-text": "Teksts", + "custom-fields": "Lauki", + "date": "Datums", + "decline": "Noraidīt", + "default-avatar": "Noklusētais attēls", + "delete": "Dzēst", + "deleteCustomFieldPopup-title": "Dzēst lauku?", + "deleteLabelPopup-title": "Dzēst birku?", + "description": "Apraksts", + "disambiguateMultiLabelPopup-title": "Skaidrot birkas darbību", + "disambiguateMultiMemberPopup-title": "Skaidrot dalībnieka darbību", + "discard": "Nomest", + "done": "Darīts", + "download": "Lejuplādēt", + "edit": "Labot", + "edit-avatar": "Mainīt attēlu", + "edit-profile": "Labot profilu", + "edit-wip-limit": "Labot WIP limitu", + "soft-wip-limit": "Maigais WIP limits", + "editCardStartDatePopup-title": "Mainīt sākuma datumu", + "editCardDueDatePopup-title": "Mainīt termiņu", + "editCustomFieldPopup-title": "Labot lauku", + "editCardSpentTimePopup-title": "Mainīt patērēto laiku", + "editLabelPopup-title": "Mainīt birku", + "editNotificationPopup-title": "Labot ziņojumu", + "editProfilePopup-title": "Labot profilu", + "email": "E-pasts", + "email-enrollAccount-subject": "Izveidots profils iekš __siteName__", + "email-enrollAccount-text": "Sveiki, __user__,\n\nLai sāktu izmantot servisu, vienkārši spied saiti zemāk.\n\n__url__\n\nPaldies.", + "email-fail": "E-pasta sūtīšana neizdevās", + "email-fail-text": "Kļūda mēģinot sūtīt e-pastu", + "email-invalid": "Nederīgs e-pasts", + "email-invite": "Ielūgt caur e-pastu", + "email-invite-subject": "__inviter__ jums atsūtīja ielūgumu", + "email-invite-text": "Sveiki, __user__,\n\n__inviter__ ielūdz jūs pievienoties dēlim \"__board__\".\n\nLūdzu sekojiet saitei zemāk:\n\n__url__\n\nPaldies.", + "email-resetPassword-subject": "Atiestatīt jūsu paroli priekš __siteName__", + "email-resetPassword-text": "Sveiki, __user__,\n\nLai atiestatītu paroli, vienkārši sekojiet saitei zemāk.\n\n__url__\n\nPaldies.", + "email-sent": "E-pasts nosūtīts", + "email-verifyEmail-subject": "Apstipriniet savu e-pasta adresi priekš __siteName__", + "email-verifyEmail-text": "Sveiki, __user__,\n\nLai apstiprinātu savu e-pasta adresi, vienkārši spiediet uz saites zemāk.\n\n__url__\n\nPaldies.", + "enable-wip-limit": "Ieslēgt WIP limitu", + "error-board-doesNotExist": "Šis dēlis neeksistē", + "error-board-notAdmin": "Jums jābūt dēļa administratoram, lai veiktu šo darbību", + "error-board-notAMember": "Jums jābūt šī dēļa dalībniekam, lai veiktu šo darbību", + "error-json-malformed": "Teksts nav derīgs JSON", + "error-json-schema": "JSON dati nesatur nepieciešamo informāciju pareizajā formātā", + "error-csv-schema": "CSV (Comma Separated Values)/TSV (Tab Separated Values) dati nesatur nepieciešamo informāciju pareizajā formātā.", + "error-list-doesNotExist": "Šis saraksts neeksistē", + "error-user-doesNotExist": "Šis lietotājs neeksistē", + "error-user-notAllowSelf": "Jūs nevarat ielūgt sevi", + "error-user-notCreated": "Šis lietotājs nav izveidots", + "error-username-taken": "Šis lieotājvārds jau ir aizņemts", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "E-pasts jau ir izmantots", + "export-board": "Eksportēt dēli", + "export-board-json": "Eksportēt dēli kā JSON", + "export-board-csv": "Eksportēt dēli kā CSV", + "export-board-tsv": "Eksportēt dēli kā TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Eksportēt dēli kā HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Eksportēt dēli", + "exportCardPopup-title": "Export card", + "sort": "Kārtot", + "sort-desc": "Spiediet, lai kārtotu sarakstu", + "list-sort-by": "Kārtot sarakstu pēc:", + "list-label-modifiedAt": "Uzskaitīt piekļuves laiku", + "list-label-title": "Saraksta nosaukums", + "list-label-sort": "Jūsu izvēlēta secība", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-no-label": "No label", - "filter-no-member": "No member", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "hide-system-messages": "Hide system messages", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "members": "Members", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "multi-selection": "Multi-Selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creater or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove Cover", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "username": "Username", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "filter": "Filtrēt", + "filter-cards": "Filtrēt kartiņas vai sarakstus", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filtrēt sarakstus pēc virsraksta", + "filter-clear": "Notīrīt filtru", + "filter-labels-label": "Filtrēt pēc birkas", + "filter-no-label": "Nav birkas", + "filter-member-label": "Filtrēt pēc dalībniekiem", + "filter-no-member": "Nav dalībnieku", + "filter-assignee-label": "Filtrēt pēc īpašnieka", + "filter-no-assignee": "Nav atbildīgā", + "filter-custom-fields-label": "Filtrēt pēc papildlaukiem", + "filter-no-custom-fields": "Nav papildu lauku", + "filter-show-archive": "Rādīt arhivētos sarakstus", + "filter-hide-empty": "Slēpt tukšus sarakstus", + "filter-on": "Filtrs ir ieslēgts", + "filter-on-desc": "Kartiņas šajā dēlī ir filtrētas. Spiediet šeit, lai labotu filtru.", + "filter-to-selection": "Filtrs uz iezīmēto", + "other-filters-label": "Citi filtri", + "advanced-filter-label": "Papildu filtri", + "advanced-filter-description": "Papildu filtri ļauj meklēt ar šādiem operatoriem: == != <= >= && || ( ) Starp operatoriem jāatstāj atstarpe. Lai filtrētu papildlaukus, norādiet tā nosaukumu un vērtību. Piemērs: Lauks1 == Vērtība1. Piezīme: ja lauki vai vērtības satur atstarpes, ievietojiet tekstu pēdiņās. Piemērs: 'Lauks 1' == 'Vērtība 1'. Lai ievadītu kontrolsimbolus (pēdiņās un slīpsvītras), pirms tiem ievadiet \\. Piemērs: Lauks1 == 'Rībēj\\' Rīga'. Papildus var kombinēt vairākus nosacījumus. Piemērs: L1 == V1 || L1 == V2. Parasti operatori tiek interpretēti no kreisās uz labo pusi. Secību var mainīt ar iekavām. Piemērs: L1 == V1 && ( L2 == V2 || L2 == V3 ). Papildus teksta laukus var filtrēt ar RegEx: L1 == /Tes.*/i", + "fullname": "Pilnais vārds un uzvārds", + "header-logo-title": "Atpakaļ uz jūsu dēļiem", + "hide-system-messages": "Slēpt darbības", + "headerBarCreateBoardPopup-title": "Izveidot dēli", + "home": "Sākums", + "import": "Importēt", + "impersonate-user": "Uzdoties par lietotāju", + "link": "Saite", + "import-board": "Importēt dēli", + "import-board-c": "Importēt dēli", + "import-board-title-trello": "Importēt dēli no Trello", + "import-board-title-wekan": "Importēt dēli no iepriekšēja eksporta", + "import-board-title-csv": "Importēt dēli no CSV/TSV", + "from-trello": "No Trello", + "from-wekan": "No iepriekšēja eksporta", + "from-csv": "No CSV/TSV", + "import-board-instruction-trello": "Jūsu Trello dēlī ejiet uz 'Menu', tālāk 'More', 'Print and Export', 'Export JSON' un nokopejiet tekstu.", + "import-board-instruction-csv": "Ielīmējiet jūsu Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "Jūsu dēlī ejiet uz 'Menu', tālāk 'Export board' un nokopējiet tekstu no lejuplādētā faila.", + "import-board-instruction-about-errors": "Ja importējot redzat kļūdu, imports tik un tā varēja nostrādāt. Pārbaudiet lapu Visi dēļi.", + "import-json-placeholder": "Šeit ielīmējiet derīgu JSON tekstu", + "import-csv-placeholder": "Šeit ielīmējiet derīgu CSV/TSV tekstu", + "import-map-members": "Norādīt dalībniekus", + "import-members-map": "Jūsu importētajam dēlim ir dalībnieki. Lūdzu norādiet kuri no šiem dalībniekiem ir kuri.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Pārbaudīt norādītos dalībniekus", + "import-user-select": "Izvēlieties kuru no esošajiem dalībniekiem izmantot kā šo lietotāju", + "importMapMembersAddPopup-title": "Izvēlēties dalībnieku", + "info": "Versija", + "initials": "Iniciāļi", + "invalid-date": "Nederīgs datums", + "invalid-time": "Nederīgs laiks", + "invalid-user": "Nederīgs dalībnieks", + "joined": "pievienojās", + "just-invited": "Jūs ielūdza pievienoties šim dēlim", + "keyboard-shortcuts": "Karstie taustiņi", + "label-create": "Izveidot birku", + "label-default": "%s birka (noklusēti)", + "label-delete-pop": "Darbību nevar atsaukt. Ar šo birka tiks noņemta no visām kartiņām un vēsture tiks dzēsta.", + "labels": "Birkas", + "language": "Valoda", + "last-admin-desc": "Nevar nomainīt lomu, jo jābūt vismaz vienam administratoram.", + "leave-board": "Pamest dēli", + "leave-board-pop": "Vai tiešām pamest __boardTitle__? Jūs tiksiet noņemts no visām kartiņām šajā dēlī.", + "leaveBoardPopup-title": "Pamest dēli?", + "link-card": "Saite uz kartiņu", + "list-archive-cards": "Pārvietot visas kartiņas no šī saraksta uz arhīvu", + "list-archive-cards-pop": "Šis visas saraksta kartiņas noņems no dēļa. Lai kartiņas apskatītu un atgrieztu ejiet uz \"Izvēlne\" > \"Arhīvs\".", + "list-move-cards": "Pārvietot visas kartiņas šajā sarakstā", + "list-select-cards": "Atzīmēt visas kartiņas šajā sarakstā", + "set-color-list": "Mainīt krāsu", + "listActionPopup-title": "Saraksta darbības", + "settingsUserPopup-title": "Lietotāja iestatījumi", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Joslas darbības", + "swimlaneAddPopup-title": "Pievienot joslu zem šīs", + "listImportCardPopup-title": "Importēt Trello kartiņu", + "listImportCardsTsvPopup-title": "Importēt Excel CSV/TSV", + "listMorePopup-title": "Vairāk", + "link-list": "Saite uz šo sarakstu", + "list-delete-pop": "Visas darbības no saraksta tiks dzēstas un nebūs atgriežamas. Darbību nevar atsaukt.", + "list-delete-suggest-archive": "Lai saglabātu darbības, sarakstu var pārvieto uz arhīvu.", + "lists": "Saraksti", + "swimlanes": "Joslas", + "log-out": "Izrakstīties", + "log-in": "Pierakstīties", + "loginPopup-title": "Pierakstīties", + "memberMenuPopup-title": "Dalībnieka iestatījumi", + "members": "Dalībnieki", + "menu": "Izvēlne", + "move-selection": "Pārvietot atzīmēto", + "moveCardPopup-title": "Pārvietot kartiņu", + "moveCardToBottom-title": "Pārvietot uz apakšu", + "moveCardToTop-title": "Pārvietot uz augšu", + "moveSelectionPopup-title": "Pārvietot atzīmēto", + "multi-selection": "Atzīmēt", + "multi-selection-label": "Pielikt birku atzīmētajam", + "multi-selection-member": "Pielikt dalībnieku atzīmētajam", + "multi-selection-on": "Atzīmēšana ieslēgta", + "muted": "Apklusināts", + "muted-info": "Nekad nesaņemsiet ziņojumus par izmaiņām šajā dēlī", + "my-boards": "Mani dēļi", + "name": "Vārds", + "no-archived-cards": "Arhīvā nav kartiņu.", + "no-archived-lists": "Arhīvā nav sarakstu.", + "no-archived-swimlanes": "Arhīvā nav joslu.", + "no-results": "Nav rezultātu", + "normal": "Normāls", + "normal-desc": "Var skatīties un labot kartiņas. Nevar mainīt iestatījumus.", + "not-accepted-yet": "Ielūgums nav vēl apstiprināts", + "notify-participate": "Saņemt ziņojumus par izmaiņām kartiņās kurām esat veidotājs vai dalībnieks", + "notify-watch": "Saņemt ziņojumus no dēļiem, sarakstiem vai kartiņām kurām sekojat", + "optional": "nav obligāts", + "or": "vai", + "page-maybe-private": "Ši lapa ir privāta. To var apskatīt pēc <a href='%s'>pierakstīšanās</a>.", + "page-not-found": "Lapa nav atrasta.", + "password": "Parole", + "paste-or-dragdrop": "lai ielīmētu, ievilktu attēla failu tajā (tikai attēlu)", + "participating": "Piedalās", + "preview": "Priekšskatījums", + "previewAttachedImagePopup-title": "Priekšskatījums", + "previewClipboardImagePopup-title": "Priekšskatījums", + "private": "Privāts", + "private-desc": "Šis dēlis ir privāts. Tikai dalībnieki var to skatīties un labot.", + "profile": "Profils", + "public": "Publisks", + "public-desc": "Šis dēlis ir publisks. Tas ir redzams visiem kam uz to ir saite un to varēs atrast ar meklēšanas dziņiem kā Google. Tikai dalībnieki var dēli labot.", + "quick-access-description": "Atzīmē dēļus ar zvaigzni, lai tie parādītos šeit.", + "remove-cover": "Noņemt vāku", + "remove-from-board": "Noņemt no dēļa", + "remove-label": "Noņemt birku", + "listDeletePopup-title": "Dzēst sarakstu?", + "remove-member": "Noņemt dalībnieku", + "remove-member-from-card": "Noņemt no kartiņas", + "remove-member-pop": "Noņemt __name__ (__username__) no __boardTitle__? Dalībnieks tiks noņemts no visām kartiņām šajā dēlī. Dalībnieks saņems ziņojumu.", + "removeMemberPopup-title": "Noņemt dalībnieku?", + "rename": "Pārsaukt", + "rename-board": "Pārsaukt dēli", + "restore": "Atjaunot", + "save": "Saglabāt", + "search": "Meklēt", + "rules": "Nosacījumi", + "search-cards": "Meklēt pa kartiņu/sarakstu virsrakstiem, aprakstiem un laukiem šajā dēlī", + "search-example": "Ievadiet meklējamo tekstu un spiediet Enter", + "select-color": "Mainīt krāsu", + "select-board": "Select Board", + "set-wip-limit-value": "Iestatīt maksimālo skaitu ar kartiņām šajā sarakstā", + "setWipLimitPopup-title": "Uzlikt WIP ierobežojumu", + "shortcut-assign-self": "Pievienot sevi atvērtajai kartiņai", + "shortcut-autocomplete-emoji": "Emoji automātiska pabeigšana", + "shortcut-autocomplete-members": "Dalībnieku automātiska pabeigšana", + "shortcut-clear-filters": "Notīrīt visus filtrus", + "shortcut-close-dialog": "Aizvērt vaicājumu", + "shortcut-filter-my-cards": "Filtrēt manas kartiņas", + "shortcut-show-shortcuts": "Atvērt saīšņu sarakstu", + "shortcut-toggle-filterbar": "Pārslēgt filtru sānjoslu", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Pārslēgt dēļu sānjoslu", + "show-cards-minimum-count": "Rādīt kartiņu skaitu, ja to sarakstā ir vairāk par", + "sidebar-open": "Atvērt sānjoslu", + "sidebar-close": "Aizvērt sānjoslu", + "signupPopup-title": "Izveidot profilu", + "star-board-title": "Spied, lai dēli atzīmētu ar zvaigzni. Šis dēlis pēc tam vienmēr būs redzams ekrāna augšā.", + "starred-boards": "Dēļi ar zvaigzni", + "starred-boards-description": "Ar zvaigzni atzīmētie dēļi vienmēr redzami ekrāna augšā", + "subscribe": "Pierakstīties", + "team": "Komanda", + "this-board": "šis dēlis", + "this-card": "šī kartiņa", + "spent-time-hours": "Pavadītais laiks (stundas)", + "overtime-hours": "Virsstundas (stundas)", + "overtime": "Virsstundas", + "has-overtime-cards": "Kartiņas ar virsstundām", + "has-spenttime-cards": "Kartiņas ar pavadīto laiku", + "time": "Laiks", + "title": "Nosaukums", + "tracking": "Sekošana", + "tracking-info": "Saņemsiet ziņojumu par jebkādām izmaiņām kartiņās, kurām esat vai nu veidotājs vai nu dalībnieks.", + "type": "Veids", + "unassign-member": "Noņemt dalībnieku", + "unsaved-description": "Jums ir nesaglabāts apraksts.", + "unwatch": "Beigt sekošanu", + "upload": "Augšuplādēt", + "upload-avatar": "Augšuplādēt attēlu", + "uploaded-avatar": "Attēls augšuplādēts", + "custom-top-left-corner-logo-image-url": "Logo attēls augšējā kreisā stūrī - URL", + "custom-top-left-corner-logo-link-url": "Logo saite augšējā kreisajā stūrī - URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Logo pierakstīšanās ekrānā - URL", + "custom-login-logo-link-url": "Logo saite pierakstīšanās ekrānā - URL", + "text-below-custom-login-logo": "Teksts zem logo pierakstīšanās lapā", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Lietotājvārds", + "import-usernames": "Import Usernames", + "view-it": "Skatīt", + "warn-list-archived": "brīdinājums: šī kartiņa ir sarakstā arhīvā", + "watch": "Sekot", + "watching": "Pilna sekošana", + "watching-info": "Saņemsiet ziņojumu par jebkādām izmaiņām dēlī", + "welcome-board": "Sākuma dēlis", + "welcome-swimlane": "Sasniegums 1", + "welcome-list1": "Pamati", + "welcome-list2": "Papildu", + "card-templates-swimlane": "Kartiņu sagataves", + "list-templates-swimlane": "Sarakstu sagataves", + "board-templates-swimlane": "Dēļu sagataves", + "what-to-do": "Ko vēlaties darīt?", + "wipLimitErrorPopup-title": "Nederīgs WIP ierobežojums", + "wipLimitErrorPopup-dialog-pt1": "Sarakstā kartiņu skaits ir lielāks kā definētais WIP ierobežojums.", + "wipLimitErrorPopup-dialog-pt2": "Lūdzu pārvietojiet kādas kartiņas prom no šī saraksta vai arī uzstādiet augstāku WIP ierobežojumu.", + "admin-panel": "Administratora panelis", + "settings": "Iestatījumi", + "people": "Dalībnieki", + "registration": "Reģistrācija", + "disable-self-registration": "Atspējot dalībnieku patstāvīgu reģistrāciju", + "invite": "Ielūgt", + "invite-people": "Ielūgt dalībniekus", + "to-boards": "Uz dēli (dēļiem)", + "email-addresses": "E-pasta adreses", + "smtp-host-description": "Adrese SMTP serverim, kas izsūtīs e-pastus.", + "smtp-port-description": "SMTP servera ports priekš e-pastu sūtīšanas.", + "smtp-tls-description": "Sūtīt e-pastus caur TLS", + "smtp-host": "SMTP hosts", + "smtp-port": "SMTP ports", + "smtp-username": "Lietotājvārds", + "smtp-password": "Parole", + "smtp-tls": "TLS atbalsts", + "send-from": "No", + "send-smtp-test": "Sūtīt sev testa e-pastu", + "invitation-code": "Ielūguma kods", + "email-invite-register-subject": "__inviter__ jums atsūtīja ielūgumu", + "email-invite-register-text": "Sveki, __user__,\n\n__inviter__ ielūdz jūs uz kanban dēli sadarbībai.\n\nLūdzu sekojiet saitei zemāk:\n__url__\n\nJūsu ielūguma kods ir: __icode__\n\nPaldies.", + "email-smtp-test-subject": "SMTP testa e-pasts", + "email-smtp-test-text": "Jūs veiksmīgi nosūtījāt e-pastu", + "error-invitation-code-not-exist": "Ielūguma kods nepastāv", + "error-notAuthorized": "Jums nav atļaujas skatīt šo lapu.", + "webhook-title": "Webhook nosaukums", + "webhook-token": "Žetons (autentifikācijai)", + "outgoing-webhooks": "Izejošie Webhook", + "bidirectional-webhooks": "Divvirziena Webhook", + "outgoingWebhooksPopup-title": "Izejošie Webhook", + "boardCardTitlePopup-title": "Kartiņu virsraksta filtrs", + "disable-webhook": "Atspējot šo Webhook", + "global-webhook": "Globālie Webhook", + "new-outgoing-webhook": "Jauns izejošais Webhook", + "no-name": "(Nezināms)", + "Node_version": "Node versija", + "Meteor_version": "Meteor versija", + "MongoDB_version": "MongoDB versija", + "MongoDB_storage_engine": "MongoDB dzinis", + "MongoDB_Oplog_enabled": "MongoDB Oplog iespējots", "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value.", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "OS_Cpus": "OS CPU skaits", + "OS_Freemem": "OS brīva atmiņa", + "OS_Loadavg": "OS vidējā noslodze", + "OS_Platform": "OS platforma", + "OS_Release": "OS relīze", + "OS_Totalmem": "OS atmiņa kopā", + "OS_Type": "OS veids", + "OS_Uptime": "OS uptime", + "days": "dienas", + "hours": "stundas", + "minutes": "minūtes", + "seconds": "sekundes", + "show-field-on-card": "Rādīt šo lauku kartiņā", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Rādīt lauku nosaukumus mini kartiņā", + "yes": "Jā", + "no": "Nē", + "accounts": "Konti", + "accounts-allowEmailChange": "Ļaut e-pasta maiņu", + "accounts-allowUserNameChange": "Ļaut lietotājvārda maiņu", + "createdAt": "Izveidots", + "modifiedAt": "Modified at", + "verified": "Apstiprināts", + "active": "Aktīvs", + "card-received": "Saņemts", + "card-received-on": "Saņemts", + "card-end": "Beigas", + "card-end-on": "Beidzas", + "editCardReceivedDatePopup-title": "Mainīt saņemšanas datumu", + "editCardEndDatePopup-title": "Mainīt beigu datumu", + "setCardColorPopup-title": "Mainīt krāsu", + "setCardActionsColorPopup-title": "Izvēlieties krāsu", + "setSwimlaneColorPopup-title": "Izvēlieties krāsu", + "setListColorPopup-title": "Izvēlieties krāsu", + "assigned-by": "Īpašnieks", + "requested-by": "Pieprasītājs", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Dzēšana nav atsaucama. Zaudēsiet visus sarakstus, kartiņas un darbības šajā dēlī.", + "delete-board-confirm-popup": "Visi saraksti, kartiņas, birkas un darbības tiks dzēstas un dēli nevarēs atgūt. Darbība nav atsaucama.", + "boardDeletePopup-title": "Dzēst dēli?", + "delete-board": "Dzēst dēli", + "default-subtasks-board": "Apakšuzdevumi priekš __board__ dēļa", + "default": "Noklusēts", + "queue": "Rinda", + "subtask-settings": "Apakšuzdevumu iestatījumi", + "card-settings": "Kartiņas iestatījumi", + "boardSubtaskSettingsPopup-title": "Dēļa apakšuzdevumu iestatījumi", + "boardCardSettingsPopup-title": "Kartiņas iestatījumi", + "deposit-subtasks-board": "Noguldīt apakšuzdevumus uz šo dēli:", + "deposit-subtasks-list": "Saraksts priekš apakšuzdevumiem noguldīts šeit:", + "show-parent-in-minicard": "Rādīt piederību mini kartiņā:", + "prefix-with-full-path": "Priedēklis ar pilnu ceļu", + "prefix-with-parent": "Priedēklis ar piederību", + "subtext-with-full-path": "Apakšteksts ar pilnu ceļu", + "subtext-with-parent": "Apakšteksts ar piederību", + "change-card-parent": "Mainīt kartiņas piederību", + "parent-card": "Pieder kartiņai", + "source-board": "Sākuma dēlis", + "no-parent": "Nerādīt piederību", + "activity-added-label": "pievinoja birku '%s' pie %s", + "activity-removed-label": "noņēma birku '%s' no %s", + "activity-delete-attach": "dzēsa failu no %s", + "activity-added-label-card": "pievienoja birku '%s'", + "activity-removed-label-card": "noņēma birku '%s'", + "activity-delete-attach-card": "dzēsa failu", + "activity-set-customfield": "uzstadīja lauku '%s' uz '%s' iekš %s", + "activity-unset-customfield": "noņēma lauku '%s' iekš %s", + "r-rule": "Nosacījums", + "r-add-trigger": "Pievienot trigeri", + "r-add-action": "Pievienot darbību", + "r-board-rules": "Dēļa nosacījumi", + "r-add-rule": "Pievienot nosacījumu", + "r-view-rule": "Skatīt nosacījumu", + "r-delete-rule": "Dzēst nosacījumu", + "r-new-rule-name": "Jaunā nosacījuma nosaukums", + "r-no-rules": "Nav nosacījumu", + "r-trigger": "Trigeris", + "r-action": "Darbība", + "r-when-a-card": "Kad kartiņa", + "r-is": "ir", + "r-is-moved": "ir pārvitots", + "r-added-to": "Pievienots", + "r-removed-from": "Noņemts no", + "r-the-board": "dēlis", + "r-list": "saraksts", + "list": "List", + "set-filter": "Uzstādīt filtru", + "r-moved-to": "Pārvietots uz", + "r-moved-from": "Pārvietots no", + "r-archived": "Pārvietots uz arhīvu", + "r-unarchived": "Atjaunots no arhīva", + "r-a-card": "kartiņa", + "r-when-a-label-is": "Kad birka ir", + "r-when-the-label": "Kad birka", + "r-list-name": "saraksta nosaukums", + "r-when-a-member": "Kad dalībnieks ir", + "r-when-the-member": "Kad dalībnieks", + "r-name": "vārds", + "r-when-a-attach": "Kad fails", + "r-when-a-checklist": "Kad kontrolsaraksts ir", + "r-when-the-checklist": "Kad kontrolsaraksts", + "r-completed": "Pabeigts", + "r-made-incomplete": "Padarīts par nepabeigtu", + "r-when-a-item": "Kad kontrolsaraksta elements ir", + "r-when-the-item": "Kad kontrolsaraksta elements", + "r-checked": "Atzīmēts", + "r-unchecked": "Atzīme noņemta", + "r-move-card-to": "Pārvietot kartiņu uz", + "r-top-of": "Augša", + "r-bottom-of": "Apakša", + "r-its-list": "tā saraksts", + "r-archive": "Pārvietot uz arhīvu", + "r-unarchive": "Atjaunot no arhīva", + "r-card": "kartiņa", + "r-add": "Pievienot", + "r-remove": "Noņemt", + "r-label": "birka", + "r-member": "dalībnieks", + "r-remove-all": "Noņemt visus dalībniekus no kartiņas", + "r-set-color": "Uzstādīt krāsu", + "r-checklist": "kontrolsaraksts", + "r-check-all": "Atzīmēt visu", + "r-uncheck-all": "Noņemt visas atzīmes", + "r-items-check": "Kontrolsaraksta elementi", + "r-check": "Atzīmēt", + "r-uncheck": "Noņemt atzimi", + "r-item": "elements", + "r-of-checklist": "kontrolsaraksta", + "r-send-email": "Sūtīt e-pastu", + "r-to": "uz", + "r-of": "no", + "r-subject": "subjekts", + "r-rule-details": "Nosacījuma detaļas", + "r-d-move-to-top-gen": "Pārvietot kartiņu uz augšu", + "r-d-move-to-top-spec": "Pārvietot kartiņu uz augšu", + "r-d-move-to-bottom-gen": "Pārvietot kartiņu uz apakšu", + "r-d-move-to-bottom-spec": "Pārvietot kartiņu uz apakšu", + "r-d-send-email": "Sūtīt e-pastu", + "r-d-send-email-to": "uz", + "r-d-send-email-subject": "subjekts", + "r-d-send-email-message": "ziņojums", + "r-d-archive": "Pārvietot kartiņu uz arhīvu", + "r-d-unarchive": "Atjaunot kartiņu no arhīva", + "r-d-add-label": "Pievienot birku", + "r-d-remove-label": "Noņemt birku", + "r-create-card": "Izveidot jaunu kartiņu", + "r-in-list": "sarakstā", + "r-in-swimlane": "joslā", + "r-d-add-member": "Pievienot dalībnieku", + "r-d-remove-member": "Noņemt dalībnieku", + "r-d-remove-all-member": "Noņemt visus dalībniekus", + "r-d-check-all": "Atzīmēt visus saraksta elementus", + "r-d-uncheck-all": "Noņemt visas saraksta atzīmes", + "r-d-check-one": "Atzīmēt elementu", + "r-d-uncheck-one": "Noņemt elementa atzīmi", + "r-d-check-of-list": "kontrolsaraksta", + "r-d-add-checklist": "Pievienot kontrolsarakstu", + "r-d-remove-checklist": "Noņemt kontrolsarakstu", + "r-by": "no", + "r-add-checklist": "Pievienot kontrolsarakstu", + "r-with-items": "ar elementiem", + "r-items-list": "elements1,elements2,elements3", + "r-add-swimlane": "Pievienot joslu", + "r-swimlane-name": "joslas nosaukums", + "r-board-note": "Piezīme: atstājiet lauku tuksu lai atgrieztu visas iespējamās vērtības.", + "r-checklist-note": "Piezīme: kontrolsaraksta elementiem jābūt norādītiem atdalītiem ar komatu.", + "r-when-a-card-is-moved": "Kad kartiņa ir pārvietota uz citu sarakstu", + "r-set": "Uzstādīt", + "r-update": "Atjaunot", + "r-datefield": "datuma lauks", + "r-df-start-at": "sākums", + "r-df-due-at": "līdz", + "r-df-end-at": "beigas", + "r-df-received-at": "saņemts", + "r-to-current-datetime": "līdz šim laikam", + "r-remove-value-from": "Noņemt vērtību no", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "layout": "Layout", - "hide-logo": "Hide Logo", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "authentication-method": "Autentifikācijas metode", + "authentication-type": "Autentifikācijas veids", + "custom-product-name": "Personalizēts produkta nosaukums", + "layout": "Izkārtojums", + "hide-logo": "Paslēt logo", + "add-custom-html-after-body-start": "Pievienot personalizētu HTML pēc <body> sākuma", + "add-custom-html-before-body-end": "Pievienot personalizētu HTML pirms </body> beigām", + "error-undefined": "Kaut kas nogāja greizi", + "error-ldap-login": "Pierakstoties notika kļūda", + "display-authentication-method": "Rādīt autentifikācijas metodi", + "default-authentication-method": "Noklusētā autentifikācijas metode", + "duplicate-board": "Dublēt dēli", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "Cilvēku skaits:", + "swimlaneDeletePopup-title": "Dzēst joslu?", + "swimlane-delete-pop": "Visas darbības tiks dzēstas no darbību saraksta un josla nebūs atjaunojama. Darbību nevar atsaukt.", + "restore-all": "Atjaunot visu", + "delete-all": "Dzēst visu", + "loading": "Notiek ielāde, lūdzu uzgaidiet.", + "previous_as": "pēdējais laiks bija", + "act-a-dueAt": "laboja nodošanas laiku uz \nKad: __timeValue__\nKur: __card__\niepriekšējais nodošanas laiks bija __timeOldValue__", + "act-a-endAt": "laboja beigu laiku uz __timeValue__ no (__timeOldValue__)", + "act-a-startAt": "mainīja sākuma laiku uz __timeValue__ no (__timeOldValue__)", + "act-a-receivedAt": "mainīja sākuma laiku uz __timeValue__ no (__timeOldValue__)", + "a-dueAt": "laboja nodošanas laiku lai tas būtu", + "a-endAt": "laboja beigu laiku", + "a-startAt": "laboja sākuma laiku", + "a-receivedAt": "laboja saņemšanas laiku lai tas būtu", + "almostdue": "tuvojas nodošanas laiks %s", + "pastdue": "pārsniegts nodošanas laiks %s", + "duenow": "nodošanas laiks ir šodien %s", + "act-newDue": "__list__/__card__ ir 1. nodošanas atgādinājums [__board__]", + "act-withDue": "__list__/__card__ nodošanas brīdinājumi [__board__]", + "act-almostdue": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ tuvojas", + "act-pastdue": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ ir pārsniegts", + "act-duenow": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ ir tagad", + "act-atUserComment": "Jūs pieminēja [__board__] __list__/__card__", + "delete-user-confirm-popup": "Tiešām dzēst kontu? Darbību nevar atsaukt.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Ļaut lietotājiem dzēst savus kontus", + "hide-minicard-label-text": "Slēpt birku tekstu mini kartiņā", + "show-desktop-drag-handles": "Rādīt darba virsmas vilkšanas simbolus", + "assignee": "Īpašnieks", + "cardAssigneesPopup-title": "Īpašnieks", + "addmore-detail": "Pievienot detalizētāku aprakstu", + "show-on-card": "Rādīt kartiņā", + "new": "Jauns", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Labot lietotāju", + "newUserPopup-title": "Jauns lietotājs", + "notifications": "Ziņojumi", + "view-all": "Skatīt visu", + "filter-by-unread": "Rādīt nelasīto", + "mark-all-as-read": "Atzīmēt visu kā lasītu", + "remove-all-read": "Noņemt visu lasīto", + "allow-rename": "Ļaut pārsaukt", + "allowRenamePopup-title": "Ļaut pārsaukt", + "start-day-of-week": "Uzstādīt nedēļas pirmo dienu", + "monday": "Pirmdiena", + "tuesday": "Otrdiena", + "wednesday": "Trešdiena", + "thursday": "Ceturtdiena", + "friday": "Piektdiena", + "saturday": "Sestdiena", + "sunday": "Svētdiena", + "status": "Statuss", + "swimlane": "Josla", + "owner": "Īpašnieks", + "last-modified-at": "Pēdējo reizi labots", + "last-activity": "Pēdējā darbība", + "voting": "Balsošana", + "archived": "Arhivēts", + "delete-linked-card-before-this-card": "Nevar dzēst šo kartiņu pirms priekšteča kartiņas dzēšnas, kurai", + "delete-linked-cards-before-this-list": "Nevar dzēst sarakstu pirms saistīto kartiņu, kas norada uz šī saraksta kartiņām, dzēšanas", + "hide-checked-items": "Slēpt atzīmētos elementus", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Kartiņa", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "saraksts", + "operator-list-abbrev": "l", + "operator-label": "birka", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "dalībnieks", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "līdz", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "līdz", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "kontrolsaraksts", + "predicate-start": "sākums", + "predicate-end": "beigas", + "predicate-assignee": "assignee", + "predicate-member": "dalībnieks", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Skaits", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/mk.i18n.json b/i18n/mk.i18n.json index d6455469f..31aaf3ce0 100644 --- a/i18n/mk.i18n.json +++ b/i18n/mk.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "\"отзавърши\" чеклистта %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Додај прилог", "add-board": "Додади Табла", + "add-template": "Add Template", "add-card": "Додади Картичка", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Додади Коридор", "add-subtask": "Додади подзадача", "add-checklist": "Додади список на задачи", @@ -113,6 +120,8 @@ "archives": "Архива", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Възложи на член от екипа", "attached": "прикачен", "attachment": "Прикаченн датотека", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Желаете ли да изтриете прикачения датотека?", "attachments": "Прикачени датотеки", "auto-watch": "Автоматично наблюдаване на таблата, когато са създадени", - "avatar-too-big": "Аватарът е прекалено голям (максимум 70KB)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Назад", "board-change-color": "Промени боја", "board-nb-stars": "%s звезди", "board-not-found": "Таблото не е најдено", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Промени името на Таблото", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Календар", "board-view-swimlanes": "Коридори", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Листи", "bucket-example": "Like “Bucket List” for example", "cancel": "Откажи", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Прикачи от", "cardCustomField-datePopup-title": "Промени датата", "cardCustomFieldsPopup-title": "Промени собствените полета", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Желаете да изтриете картата?", "cardDetailsActionsPopup-title": "Опции", "cardLabelsPopup-title": "Етикети", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Картички", "cards-count": "Картички", + "cards-count-one": "Карта", "casSignIn": "Sign In with CAS", "cardType-card": "Карта", "cardType-linkedCard": "Поврзана карта", @@ -191,6 +236,7 @@ "close": "Затвори", "close-board": "Затвори Табла", "close-board-pop": "Ще можете да възстановите Таблото като натиснете на бутона \"Архива\" в началото на хедъра.", + "close-card": "Close Card", "color-black": "црно", "color-blue": "сино", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "сегашен", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Чекбокс", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Дата", "custom-field-dropdown": "Падащо меню", "custom-field-dropdown-none": "(няма)", @@ -297,13 +345,27 @@ "error-board-notAMember": "За да направите това трябва да сте член на това табло", "error-json-malformed": "Текстът Ви не е валиден JSON", "error-json-schema": "JSON информацията Ви не съдържа информация във валиден формат", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Този списък не съществува", "error-user-doesNotExist": "Този потребител не съществува", "error-user-notAllowSelf": "Не можете да поканите себе си", "error-user-notCreated": "Този потребител не е създаден", "error-username-taken": "Това потребителско име е вече заето", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Имейлът е вече зает", "export-board": "Експортиране на Табло", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Експортиране на Табло", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Филтер", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Премахване на филтрите", + "filter-labels-label": "Filter by label", "filter-no-label": "без етикет", + "filter-member-label": "Filter by member", "filter-no-member": "без член", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "Няма Собствени полета", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Има приложени филтри", "filter-on-desc": "В момента филтрирате картите в това табло. Моля, натиснете тук, за да промените филтъра.", "filter-to-selection": "Филтрирай избраните", + "other-filters-label": "Other Filters", "advanced-filter-label": "Напреден филтер", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Име", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Креирај Табло", "home": "Почетна", "import": "Импорт", + "impersonate-user": "Impersonate user", "link": "Врска", "import-board": "Импортирай Табло", "import-board-c": "Импортирай Табло", "import-board-title-trello": "Импорт на табло от Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Импортирането ще изтрие всичката налична информация в таблото и ще я замени с нова.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "От Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Копирайте валидната Ви JSON информация тук", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Избери всички карти в този списък", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Импорт на карта от Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Още", "link-list": "Връзка към този списък", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Премести в началото", "moveSelectionPopup-title": "Move selection", "multi-selection": "Множествен избор", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Множественият избор е приложен", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Търсене", "rules": "Правила", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Избери цвят", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Въведи WIP лимит", "shortcut-assign-self": "Добави себе си към тази карта", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Филтрирай моите карти", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Отвори/затвори сайдбара с филтри", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Покажи бройката на картите, ако списъка съдържа повече от", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Качване на аватар", "uploaded-avatar": "Качихте аватар", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Потребителско име", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "внимание: тази карта е в списък во Архива", "watch": "Наблюдавай", @@ -553,7 +647,8 @@ "minutes": "минути", "seconds": "секунди", "show-field-on-card": "Покажи това поле в картата", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Да", "no": "Не", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Разреши промяна на имейла", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Създаден на", + "modifiedAt": "Modified at", "verified": "Потвърден", "active": "Активен", "card-received": "Получена", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Разпределена от", "requested-by": "Поискан от", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Изтриване на Таблото?", @@ -614,13 +711,16 @@ "r-delete-rule": "Изтрий правилото", "r-new-rule-name": "Заглавие за новото правило", "r-no-rules": "Няма правила", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "Когато карта", "r-is": "е", "r-is-moved": "преместена", - "r-added-to": "добавена в", + "r-added-to": "Added to", "r-removed-from": "премахната от", "r-the-board": "таблото", "r-list": "списък", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Детайли за правилото", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Карта", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "списък", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Број", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/mn.i18n.json b/i18n/mn.i18n.json index a38f624b2..f8adfa4a3 100644 --- a/i18n/mn.i18n.json +++ b/i18n/mn.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Хавсралт нэмэх", "add-board": "Самбар нэмэх", + "add-template": "Add Template", "add-card": "Карт нэмэх", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Чеклист нэмэх", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Самбар үүсгэх", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/nb.i18n.json b/i18n/nb.i18n.json index eb24be93b..55f0d5089 100644 --- a/i18n/nb.i18n.json +++ b/i18n/nb.i18n.json @@ -1,178 +1,223 @@ { - "accept": "Godta", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "accept": "Godkjenn", + "act-activity-notify": "Aktivitetsvarsel", + "act-addAttachment": "Lagt til vedlegg __subtask__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-deleteAttachment": "Slettet vedlegg __subtask__ fra kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addSubtask": "lagt til underoppgave __subtask__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addLabel": "Lagt til etikett __label__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addedLabel": "Lagt til etikett __label__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-removeLabel": "Fjernet etikett __label__ fra kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-removedLabel": "Fjernet etikett __label__ fra kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addChecklist": "Lagt til sjekkliste __subtask__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addChecklistItem": "Lagt til sjekklistepunkt __checklistItem__ til sjekklisten __checklist__ på kort __card__ i listen __list__ i svømmebane __swimlane__ på tavle __board__", + "act-removeChecklist": "Fjernet sjekkliste __subtask__ fra kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-removeChecklistItem": "Fjernet sjekklistepunkt __checklistItem__ fra sjekklisten __checklist__ på kort __card__ i listen __list__ i svømmebane __swimlane__ på tavle __board__", + "act-checkedItem": "avmerket __checklistItem__ av sjekklisten __checklist__ på kort __card__ i listen __list__ i svømmebane __swimlane__ på tavle __board__", + "act-uncheckedItem": "fjernet avmerking __checklistItem__ av sjekklisten __checklist__ på kort __card__ i listen __list__ i svømmebane __swimlane__ på tavle __board__", + "act-completeChecklist": "Komplettert sjekkliste __checklist__ på kort __card__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-uncompleteChecklist": "Ikke komplettert sjekkliste __checklist__ på kort __card__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-addComment": "Kommentert på kort __card__: __comment__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-editComment": "Redigert på kort __card__: __comment__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-deleteComment": "Slettet kommentar på kort __card__: __comment__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-createBoard": "opprettet tavle __board__", + "act-createSwimlane": "opprettet svømmebane __swimlane__ på tavlen __board__", + "act-createCard": "opprettet kort __card__ til liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-createCustomField": "opprettet tilpasset felt __customField__ på tavlen __board__", + "act-deleteCustomField": "slettet tilpasset felt __customField__ på tavlen __board__", + "act-setCustomField": "Redigert tilpasset felt __customField__: __customFieldValue__ på kort __card__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-createList": "opprettet liste __list__ til tavlen __board__", + "act-addBoardMember": "Opprettet Medlem __member__ til tavlen __board__", + "act-archivedBoard": "Tavle __board__ flyttet til Arkiv", + "act-archivedCard": "Kort __card__ på liste __list__ i svømmebane __swimlane__ på tavle __board__ flyttet til Arkiv", + "act-archivedList": "Liste __list__ i svømmebane __swimlane__ på tavle __board__ flyttet til arkiv", + "act-archivedSwimlane": "Svømmebane __swimlane__ på tavlen __board__ flyttet til Arkiv", + "act-importBoard": "importert tavle __board__", + "act-importCard": "importert kort __card__ til liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-importList": "importert liste __list__ til svømmebane __swimlane__ på tavle __board__", + "act-joinMember": "Lagt til Medlem __label__ til kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-moveCard": "flyttet kort __card__ på tavle __board__ fra liste __oldList__ i svømmebane __oldSwimlane__ til liste __list__ i svømmebane __swimlane__", + "act-moveCardToOtherBoard": "flyttet kort __card__ fra liste __oldList__ i svømmebane __oldSwimlane__ på tavle __oldBoard__ til liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-removeBoardMember": "Fjernet Medlem __member__ fra tavlen __board__", + "act-restoredCard": "Gjenopprettet kort __card__ til liste __list__ i svømmebane __swimlane__ på tavle __board__", + "act-unjoinMember": "Fjernet Medlem __label__ fra kort __card__ i liste __list__ i svømmebane __swimlane__ på tavle __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", + "actions": "Aksjon", "activities": "Aktiviteter", "activity": "Aktivitet", "activity-added": "la %s til %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "%s flyttet til Arkivet", "activity-attached": "la %s til %s", "activity-created": "opprettet %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "ekskluderte %s fra %s", - "activity-imported": "importerte %s til %s fra %s", - "activity-imported-board": "importerte %s fra %s", + "activity-customfield-created": "Opprettet tilpasset felt%s", + "activity-excluded": "ekskludert %s fra %s", + "activity-imported": "importert %s til %s fra %s", + "activity-imported-board": "importert %s fra %s", "activity-joined": "ble med %s", "activity-moved": "flyttet %s fra %s til %s", "activity-on": "på %s", "activity-removed": "fjernet %s fra %s", "activity-sent": "sendte %s til %s", "activity-unjoined": "forlot %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-subtask-added": "underoppgave lagt til %s", + "activity-checked-item": "sjekket %s i sjekkliste %s av %s", + "activity-unchecked-item": "ikke sjekket %s i sjekkliste %s av %s", "activity-checklist-added": "la til sjekkliste til %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "activity-checklist-removed": "flyttet en sjekkliste fra %s", + "activity-checklist-completed": "ferdigstilt sjekkliste %s av %s", + "activity-checklist-uncompleted": "ikke ferdigstilt sjekkliste %s av %s", + "activity-checklist-item-added": "lagt til sjekklisteoppgave til '%s' i %s", + "activity-checklist-item-removed": "fjernet sjekklisteoppgave fra '%s' i %s", "add": "Legg til", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-card": "Add Card", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", + "activity-checked-item-card": "sjekket %s i sjekkliste %s", + "activity-unchecked-item-card": "ikke sjekket %s i sjekkliste %s", + "activity-checklist-completed-card": "Komplettert sjekkliste __checklist__ på kort __card__ på liste __list__ i svømmebane __swimlane__ på tavle __board__", + "activity-checklist-uncompleted-card": "ikke ferdigstilt sjekkliste %s", + "activity-editComment": "redigert kommentar %s", + "activity-deleteComment": "slettet kommentar %s", + "activity-receivedDate": "redigert mottatt dato til %s av %s", + "activity-startDate": "redigert startdato til %s av %s", + "activity-dueDate": "redigert forfallsdato til %s av %s", + "activity-endDate": "redigert sluttdato %s av %s", + "add-attachment": "Legg til Vedlegg", + "add-board": "Legg til Tavle", + "add-template": "Add Template", + "add-card": "Legg til Kort", + "add-card-to-top-of-list": "Legg til Kort på Toppen av Listen", + "add-card-to-bottom-of-list": "Legg til Kort på Bunnen av Listen", + "add-swimlane": "Legg til Svømmebane", + "add-subtask": "Legg til Underoppgave", + "add-checklist": "Legg til Sjekkliste", "add-checklist-item": "Nytt punkt på sjekklisten", "add-cover": "Nytt omslag", - "add-label": "Add Label", - "add-list": "Add List", + "add-label": "Legg til Etikett", + "add-list": "Legg til Liste", "add-members": "Legg til medlemmer", "added": "Lagt til", "addMemberPopup-title": "Medlemmer", "admin": "Admin", - "admin-desc": "Kan se og redigere kort, fjerne medlemmer, og endre innstillingene for tavlen.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", + "admin-desc": "Kan se og redigere kort, fjerne medlemmer og endre innstillingene for tavlen.", + "admin-announcement": "Kunngjøring", + "admin-announcement-active": "Aktiv systemkunngjøring", + "admin-announcement-title": "Kunngjøring fra Administrator", "all-boards": "Alle tavler", "and-n-other-card": "Og __count__ andre kort", "and-n-other-card_plural": "Og __count__ andre kort", "apply": "Lagre", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", + "app-is-offline": "Laster, vennligst vent. Oppdatering av siden vil forårsake datatap. Dersom oppdatering ikke hjelper, vennligst sjekk at servertjenesten ikke har stoppet opp.", + "archive": "Flytt til Arkiv", + "archive-all": "Flytt alt til Arkiv", + "archive-board": "Flytt Tavle til Arkiv", + "archive-card": "Flytt Kort til Arkiv", + "archive-list": "Flytt Liste til Arkiv", + "archive-swimlane": "Flytt Svømmebane til Arkiv", + "archive-selection": "Flytt valgte til Arkiv", + "archiveBoardPopup-title": "Flytt Tavle til Arkiv?", "archived-items": "Arkiv", "archived-boards": "Tavler i arkivet", - "restore-board": "Restore Board", + "restore-board": "Reetabler Tavle", "no-archived-boards": "Ingen tavler i arkivet", "archives": "Arkiv", - "template": "Template", - "templates": "Templates", - "assign-member": "Tildel medlem", + "template": "Mal", + "templates": "Maler", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Tildel Medlem", "attached": "la ved", "attachment": "Vedlegg", "attachment-delete-pop": "Sletting av vedlegg er permanent og kan ikke angres", "attachmentDeletePopup-title": "Slette vedlegg?", "attachments": "Vedlegg", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "auto-watch": "Automatisk overvåkning av tavler når de opprettes", + "avatar-too-big": "Denne avataren er for stor (maks 520 kB)", "back": "Tilbake", "board-change-color": "Endre farge", "board-nb-stars": "%s stjerner", "board-not-found": "Kunne ikke finne tavlen", "board-private-info": "Denne tavlen vil være <strong>privat</strong>.", "board-public-info": "Denne tavlen vil være <strong>offentlig</strong>.", + "board-drag-drop-reorder-or-click-open": "Klikk og Dra for å omorganisere ikoner på tavlen. Klikk på ikon for å åpne tavlen.", "boardChangeColorPopup-title": "Ende tavlens bakgrunnsfarge", "boardChangeTitlePopup-title": "Endre navn på tavlen", "boardChangeVisibilityPopup-title": "Endre synlighet", "boardChangeWatchPopup-title": "Endre overvåkning", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardMenuPopup-title": "Innstillinger Tavle", + "boardChangeViewPopup-title": "Tavlevisning", "boards": "Tavler", - "board-view": "Board View", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-lists": "Lists", - "bucket-example": "Som \"Bucket List\" for eksempel", + "board-view": "Tavlevisning", + "board-view-cal": "Kalender", + "board-view-swimlanes": "Svømmebaner", + "board-view-collapse": "Slå sammen", + "board-view-gantt": "Gantt", + "board-view-lists": "Lister", + "bucket-example": "Som 'Bucket List' for eksempel", "cancel": "Avbryt", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "Dette kortet er flyttet til Arkivet", + "board-archived": "Denne tavlen er flyttet til Arkivet", "card-comments-title": "Dette kortet har %s kommentar.", "card-delete-notice": "Sletting er permanent. Du vil miste alle hendelser knyttet til dette kortet.", "card-delete-pop": "Alle handlinger vil fjernes fra feeden for aktiviteter og du vil ikke kunne åpne kortet på nytt. Det er ingen mulighet å angre.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-delete-suggest-archive": "Du kan flytte kortet til Arkivet for å fjerne det fra tavlen og bevare aktivteten.", "card-due": "Frist", - "card-due-on": "Frist til", - "card-spent": "Spent Time", + "card-due-on": "Utløpsfrist", + "card-spent": "Forbrukt tid", "card-edit-attachments": "Rediger vedlegg", - "card-edit-custom-fields": "Edit custom fields", + "card-edit-custom-fields": "Rediger tilpassede felt", "card-edit-labels": "Rediger etiketter", "card-edit-members": "Endre medlemmer", "card-labels-title": "Endre etiketter for kortet.", - "card-members-title": "Legg til eller fjern tavle-medlemmer fra dette kortet.", + "card-members-title": "Legg til eller fjern medlemmer på tavlen fra dette kortet.", "card-start": "Start", - "card-start-on": "Starter på", + "card-start-on": "Starter", "cardAttachmentsPopup-title": "Legg ved fra", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", + "cardCustomField-datePopup-title": "Endre dato", + "cardCustomFieldsPopup-title": "Rediger tilpassede felt", + "cardStartVotingPopup-title": "Start en avstemming", + "positiveVoteMembersPopup-title": "Supportere", + "negativeVoteMembersPopup-title": "Motstandere", + "card-edit-voting": "Redigere avstemming", + "editVoteEndDatePopup-title": "Endre sluttdato avstemming", + "allowNonBoardMembers": "Tillat alle innloggede Brukere", + "vote-question": "Spørsmål avstemming", + "vote-public": "Se hvem som stemte på hva", + "vote-for-it": "for", + "vote-against": "mot", + "deleteVotePopup-title": "Slett stemme?", + "vote-delete-pop": "Sletting er permanent. Du vil miste alle aksjoner som har en sammenheng med denne stemmen.", + "cardStartPlanningPokerPopup-title": "Start rangering oppgave", + "card-edit-planning-poker": "Endre rangering oppgave", + "editPokerEndDatePopup-title": "Endre sluttdato rangering ", + "poker-question": "Rangering", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Ferdig", + "poker-result-votes": "Stemmer", + "poker-result-who": "Hvem", + "poker-replay": "Ranger på nytt", + "set-estimation": "Estimat", + "deletePokerPopup-title": "Slett rangering", + "poker-delete-pop": "Sletting er permanent. Du vil miste alle aksjoner assosiert med rangeringen.", "cardDeletePopup-title": "Slett kort?", - "cardDetailsActionsPopup-title": "Kort-handlinger", + "cardDetailsActionsPopup-title": "Behandling kort", "cardLabelsPopup-title": "Etiketter", "cardMembersPopup-title": "Medlemmer", "cardMorePopup-title": "Mer", - "cardTemplatePopup-title": "Create template", + "cardTemplatePopup-title": "Opprett mal", "cards": "Kort", "cards-count": "Kort", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", + "cards-count-one": "Kort", + "casSignIn": "Logg inn med CAS", + "cardType-card": "Kort", + "cardType-linkedCard": "Linket kort", + "cardType-linkedBoard": "Linket Tavle", "change": "Endre", "change-avatar": "Endre avatar", "change-password": "Endre passord", @@ -183,17 +228,18 @@ "changePasswordPopup-title": "Endre passord", "changePermissionsPopup-title": "Endre tillatelser", "changeSettingsPopup-title": "Endre innstillinger", - "subtasks": "Deloppgave", + "subtasks": "Underoppgaver", "checklists": "Sjekklister", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "clipboard": "Clipboard or drag & drop", + "click-to-star": "Favorittmerke Tavlen", + "click-to-unstar": "Fjern favorittmerke Tavlen", + "clipboard": "Utklippstavle eller Dra og Slipp", "close": "Lukk", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-board": "Lukk Tavle", + "close-board-pop": "Du vil ha muligheten til å gjenopprette Tavle ved å klikke på 'Arkiv'-knappen i hjem-menyen.", + "close-card": "Lukk Kort", "color-black": "svart", "color-blue": "blå", - "color-crimson": "crimson", + "color-crimson": "høyrød", "color-darkgreen": "mørkegrønn", "color-gold": "gull", "color-gray": "grå", @@ -201,569 +247,816 @@ "color-indigo": "indigo", "color-lime": "lime", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", + "color-mistyrose": "rosa", "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-orange": "oransje", + "color-paleturquoise": "turkis", + "color-peachpuff": "fersken", "color-pink": "rosa", - "color-plum": "plum", + "color-plum": "plomme", "color-purple": "lilla", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", - "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", + "color-red": "rød", + "color-saddlebrown": "brun", + "color-silver": "sølv", + "color-sky": "skyblå", + "color-slateblue": "blå", + "color-white": "hvit", + "color-yellow": "gul", + "unset-color": "Slå av", + "comment": "Lagre", + "comment-placeholder": "Skriv kommentar", + "comment-only": "Kun kommentar", + "comment-only-desc": "Kun kommentar på kort.", + "no-comments": "Ingen kommentarer", + "no-comments-desc": "Kan ikke se kommentarer eller aktiviteter.", + "worker": "Arbeider", + "worker-desc": "Kan bare flytte kort, tildele kort til seg selv og kommentere.", + "computer": "Datamaskin", + "confirm-subtask-delete-dialog": "Er du sikker på at du vil slette underoppgaven?", + "confirm-checklist-delete-dialog": "Er du sikker på at du vil slette sjekklisten?", + "copy-card-link-to-clipboard": "Kopier lenke", + "linkCardPopup-title": "Link Kort", + "searchElementPopup-title": "Søk", + "copyCardPopup-title": "Kopier Kort", + "copyChecklistToManyCardsPopup-title": "Kopier Sjekklistemal til mange Kort", + "copyChecklistToManyCardsPopup-instructions": "Mottakende korttitler og beskivelse i dette JSON-formatet", + "copyChecklistToManyCardsPopup-format": "[ {\"Tittel\": \"Tittel første kort\", \"Beskrivelse\":\"Beskrivelse første kort\"}, {\"Tittel\":\"Tittel andre kort\",\"Beskrivelse\":\"Beskrivelse andre kort\"},{\"Tittel\":\"Tittel siste kort\",\"Beskrivelse\":\"Beskrivelse siste kort\"} ]", + "create": "Opprett", + "createBoardPopup-title": "Opprett Tavle", + "chooseBoardSourcePopup-title": "Importer tavle", + "createLabelPopup-title": "Opprett Etikett", + "createCustomField": "Opprett Felt", + "createCustomFieldPopup-title": "Opprett Felt", + "current": "gjeldende", + "custom-field-delete-pop": "Ingen angremulighet. Dette vil slette tilpassede felt fra alle kort og slette tilhørende historikk.", + "custom-field-checkbox": "Sjekkboks", + "custom-field-currency": "Valuta", + "custom-field-currency-option": "Valutakode", "custom-field-date": "Dato", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown": "Nedtrekksliste", + "custom-field-dropdown-none": "(ingen)", + "custom-field-dropdown-options": "Listevalg", + "custom-field-dropdown-options-placeholder": "Trykk Enter for å legge til flere valg", "custom-field-dropdown-unknown": "(ukjent)", "custom-field-number": "Nummer", "custom-field-text": "Tekst", - "custom-fields": "Custom Fields", + "custom-fields": "Tilpassede felt", "date": "Dato", "decline": "Avvis", - "default-avatar": "Default avatar", + "default-avatar": "Standard avatar", "delete": "Slett", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", + "deleteCustomFieldPopup-title": "Slett tilpassede Felt?", + "deleteLabelPopup-title": "Slett Etikett?", "description": "Beskrivelse", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", + "disambiguateMultiLabelPopup-title": "Tvetydig Etikett-aksjon", + "disambiguateMultiMemberPopup-title": "Tvetydig aksjon av Medlem", + "discard": "Forkast", + "done": "Utført", "download": "Last ned", "edit": "Rediger", "edit-avatar": "Endre avatar", "edit-profile": "Endre profil", "edit-wip-limit": "Endre WIP grense", - "soft-wip-limit": "Soft WIP Limit", + "soft-wip-limit": "Myk WIP-begrensning", "editCardStartDatePopup-title": "Endre start dato", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", + "editCardDueDatePopup-title": "Endre forfallsdato", + "editCustomFieldPopup-title": "Redigere felt", + "editCardSpentTimePopup-title": "Endre forbrukt tid", + "editLabelPopup-title": "Endre Etikett", + "editNotificationPopup-title": "Endre Varsel", "editProfilePopup-title": "Endre profil", - "email": "Email", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-wip-limit": "Enable WIP Limit", + "email": "E-post", + "email-enrollAccount-subject": "En brukerkonto er opprettet for deg på __siteName__", + "email-enrollAccount-text": "Hei __user__,\n\nFor å ta i bruk tjenesten, klikk på linken under.\n\n__url__\n\nTakk.", + "email-fail": "Sending av epost feilet", + "email-fail-text": "Feil ved forsøk på sending av e-post", + "email-invalid": "Ugyldig e-post", + "email-invite": "Inviter via e-post", + "email-invite-subject": "__inviter__ sendte en invitasjon til deg", + "email-invite-text": "Hei __user__,\n\n__inviter__ inviterer det til å delta på Tavlen \"__board__\" for samhandling.\n\nVennligst følg lenken under:\n\n__url__\n\nTakk.", + "email-resetPassword-subject": "Resett passord på __siteName__", + "email-resetPassword-text": "Hei __user__,\n\nFor gjenoppretting av ditt passord, klikk på lenken under.\n\n__url__\n\nTakk.", + "email-sent": "E-post sendt", + "email-verifyEmail-subject": "Verifiser din e-postadresse på __siteName__", + "email-verifyEmail-text": "Hei __user__,\n\nFor å verifisere e-postsdressen din, klikk på lenken under.\n\n__url__\n\nTakk.", + "enable-wip-limit": "Aktiver WIP-begrensning", "error-board-doesNotExist": "Denne tavlen finnes ikke", "error-board-notAdmin": "Du må være administrator for denne tavlen for å gjøre dette", - "error-board-notAMember": "Du må være medlem av denne tavlen for å gjøre dette", + "error-board-notAMember": "Du må være Medlem på denne tavlen for å gjøre dette", "error-json-malformed": "Denne teksten er ikke gyldig JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-json-schema": "Dine JSON-data inneholder ikke informasjon i korrekt format", + "error-csv-schema": "Dine CSV- (kommaseparerte verdier) / TSV-(Tab-separerte verdier) inneholder ikke informasjon i korrekt format", "error-list-doesNotExist": "Denne listen finnes ikke", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "error-user-doesNotExist": "Denne Brukeren eksisterer ikke", + "error-user-notAllowSelf": "Du kan ikke invitere deg selv", + "error-user-notCreated": "Denne Brukeren er ikke opprettet", + "error-username-taken": "Brukernavn allerede i bruk", + "error-orgname-taken": "Navn på Organisasjon allerede benyttet", + "error-teamname-taken": "Navn på Team allerede benyttet", + "error-email-taken": "E-postadressen er allerede i bruk", + "export-board": "Eksporter tavle", + "export-board-json": "Eksporter tavle til JSON", + "export-board-csv": "Eksporter tavle til CSV", + "export-board-tsv": "Eksporter tavle til TSV", + "export-board-excel": "Eksporter Tavle til Excel", + "user-can-not-export-excel": "Bruker kan ikke eksportere til Excel", + "export-board-html": "Eksporter tavle til HTML", + "export-card": "Eksportér kort", + "export-card-pdf": "Eksportér kort til PDF", + "user-can-not-export-card-to-pdf": "Brukeren kan ikke eksportere kort til PDF", + "exportBoardPopup-title": "Eksporter tavle", + "exportCardPopup-title": "Eksportér kort", + "sort": "Sorter", + "sort-desc": "Klikk for sortering liste", + "list-sort-by": "Sorter liten etter:", + "list-label-modifiedAt": "Siste innlogging", + "list-label-title": "Navn på liste", + "list-label-sort": "Din sortering", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-no-label": "No label", - "filter-no-member": "No member", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", + "filter": "Filtrer", + "filter-cards": "Filtrer Kort eller Lister", + "filter-dates-label": "Filtrer etter dato", + "filter-no-due-date": "Ingen forfallsdato", + "filter-overdue": "Forfalt", + "filter-due-today": "Forfall i dag", + "filter-due-this-week": "Forfall denne uke", + "filter-due-tomorrow": "Forfall i morgen", + "list-filter-label": "Filtrer Liste etter tittel", + "filter-clear": "Fjern filter", + "filter-labels-label": "Filtrer etter Etikett", + "filter-no-label": "Ingen Etikett", + "filter-member-label": "Filtrer etter Medlem", + "filter-no-member": "Ingen Medlem", + "filter-assignee-label": "Filtrer etter tildelt", + "filter-no-assignee": "Ingen tildelt", + "filter-custom-fields-label": "Filtrer etter Tilpassede felt", + "filter-no-custom-fields": "Ingen Tilpassende felt", + "filter-show-archive": "Vis arkiverte lister", + "filter-hide-empty": "Skjul tomme lister", + "filter-on": "Filter aktivert", + "filter-on-desc": "Filtrering av Kort er aktivert på Tavlen. Klikk for redigering av filter.", + "filter-to-selection": "Filtrering til selektering", + "other-filters-label": "Andre filtre", + "advanced-filter-label": "Avanserte filter", + "advanced-filter-description": "Avansert filter tillater skriving av søkestrenger med følgende operatorer:\n== != <= >= && || ( ) der et mellomrom benyttes som separatorer mellom operatorer.\nDu kan filtrere på alle tilpassede felt ved å skrive navn og verdier, for eksempel: Felt1 == Verdi1. \nMerknad: Dersom felt eller verdier inneholder mellomrom må disse inkluderes innenfor fnuffer, for eksempel; 'Felt 1' == 'Verdi 1'.\nFor å utelate enkelte karakterer (' \\ /), kan du benytte \\. For eksempel: Field1 == I\\'m.\nI tillegg kan du kombinere forskjellige operatorer, for eksempel: F1 == V1 || F1 == V2.\nNormalt tolkes alle operatorene fra venstre til høyre. Du kan endre dette ved bruk av paranteser, for eksempel: F1 == V1 && ( F2 == V2 || F2 == V3 ). Videre kan du søke etter tekstfelt ved bruk av Regex, for eksempel: F1 == /Tes.*/i", + "fullname": "Fullt navn", "header-logo-title": "Tilbake til dine tavler", - "hide-system-messages": "Hide system messages", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "hide-system-messages": "Skjul systemmeldinger", + "headerBarCreateBoardPopup-title": "Opprett Tavle", + "home": "Hjem", + "import": "Importer", + "impersonate-user": "Anonymiser Bruker", + "link": "Lenke", + "import-board": "Importer Tavle", + "import-board-c": "Importer Tavle", + "import-board-title-trello": "Importer Tavle fra Trello", + "import-board-title-wekan": "Importer Tavle fra tidligere eksport", + "import-board-title-csv": "Importer tavle fra CSV/TSV", + "from-trello": "Fra Trello", + "from-wekan": "Fra tidligere eksport", + "from-csv": "Fra CSV/TSV", + "import-board-instruction-trello": "I din Trello-tavle, gå til 'Menu' -> 'More' -> 'Print og Eksporter' -> 'Eksport JSON' og kopier tekst fra eksport.", + "import-board-instruction-csv": "Lim inn kommaseparerte verdier (CSV) / tabulatorseparerte verdier (TSV).", + "import-board-instruction-wekan": "I Tavlen din, gå til 'Meny' -> 'Eksport Tavle' og kopier teksten fra den nedlastede filen.", + "import-board-instruction-about-errors": "Dersom du får feilmelding ved import av Tavle, vil importen noen ganger likevel være intakt og Tavlen finnes på 'Alle Tavler'-siden.", + "import-json-placeholder": "Lim inn gyldig JSON-datasett her", + "import-csv-placeholder": "Lim inn gyldig CSV/TSV-data her", + "import-map-members": "Tilknytt medlemmer", + "import-members-map": "Importert Tavle har medlemmer. Vennligst tilknytt medlemmer som skal importeres til dine Brukere.", + "import-members-map-note": "Merk: Ikke registrerte medlemmer vil bli lagt til nåværende bruker.", + "import-show-user-mapping": "Gjennomgang tilknytning medlemmer", + "import-user-select": "Velg eksisterende Bruker som skal tilknyttes dette Medlemmet", + "importMapMembersAddPopup-title": "Velg Medlem", + "info": "Versjon", + "initials": "Initialer", + "invalid-date": "Ugyldig dato", + "invalid-time": "Ugyldig tid", + "invalid-user": "Ugyldig bruker", + "joined": "ble med", + "just-invited": "Du har nå blitt invitert til denne Tavlen", + "keyboard-shortcuts": "Hurtigtaster", + "label-create": "Opprett Etikett", + "label-default": "%s etikett (standard)", + "label-delete-pop": "Ingen angremulighet. Dette vil slette etiketter fra alle kort og slette tilhørende historikk.", "labels": "Etiketter", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", + "language": "Språk", + "last-admin-desc": "Du kan ikke endre rolle fordi det må finnes minst én administrator.", + "leave-board": "Forlat Tavle", + "leave-board-pop": "Er du sikker på at du vil forlate __boardTitle__? Du vil bli fjernet fra alle kortene på denne tavlen.", + "leaveBoardPopup-title": "Forlat Tavle?", + "link-card": "Lenke til kortet", + "list-archive-cards": "Flytt alle kortene i denne listen til Arkivet", + "list-archive-cards-pop": "Dette vil fjerne alle kortene i denne listen fra denne Tavlen. For å se kortene i Arkivet og flytte dem tilbake til Tavlen, klikk \"Meny\" -> \"Arkiv\".", + "list-move-cards": "Flytt alle kortene i denne listen", + "list-select-cards": "Velg alle kortene i denne listen", + "set-color-list": "Sett farge", + "listActionPopup-title": "List aksjoner", + "settingsUserPopup-title": "Brukerinnstillinger", + "settingsTeamPopup-title": "Innstillinger Team", + "settingsOrgPopup-title": "Innstillinger Organisasjon", + "swimlaneActionPopup-title": "Aksjoner Svømmebane", + "swimlaneAddPopup-title": "Legg til en Svømmebane under", + "listImportCardPopup-title": "Importer et Trello-kort", + "listImportCardsTsvPopup-title": "Importer Excel CSV/TSV", "listMorePopup-title": "Mer", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", + "link-list": "Lenke til liste", + "list-delete-pop": "Alle aksjoner vil fjernes fra aktivitetsstrømmen og du vil ikke kunne gjenopprette listen. Det finnes ingen angremulighet.", + "list-delete-suggest-archive": "Du kan flytte en liste til Arkiv for å fjerne den fra Tavlen og bevare aktivitetene.", + "lists": "Lister", + "swimlanes": "Svømmebaner", + "log-out": "Logg ut", + "log-in": "Logg inn", + "loginPopup-title": "Logg inn", + "memberMenuPopup-title": "Innstillinger Medlem", "members": "Medlemmer", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "multi-selection": "Multi-Selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", + "menu": "Meny", + "move-selection": "Flytt valgte", + "moveCardPopup-title": "Flytt Kort", + "moveCardToBottom-title": "Flytt til bunnen", + "moveCardToTop-title": "Flytt til toppen", + "moveSelectionPopup-title": "Flytt valgte", + "multi-selection": "Velg flere", + "multi-selection-label": "Sett Etikett for valgte", + "multi-selection-member": "Sett Medlem for valgte", + "multi-selection-on": "Valg av flere er aktivert", + "muted": "Dempet", + "muted-info": "Du vil ikke bli varslet om endringer på denne Tavlen", "my-boards": "Mine tavler", "name": "Navn", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", + "no-archived-cards": "Ingen Kort i Arkivet", + "no-archived-lists": "Ingen Lister i Arkivet", + "no-archived-swimlanes": "Ingen Svømmebaner i Arkivet", + "no-results": "Ingen resultat", "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creater or member", + "normal-desc": "Kan se og redigere Kort. Kan ikke endre innstillinger.", + "not-accepted-yet": "Invitasjon foreløpig ikke akseptert", + "notify-participate": "Motta oppdateringer på alle kort du bidrar til som Oppretter eller Medlem", "notify-watch": "Motta oppdatering av alle tavler, lister eller kort som du overvåker", "optional": "valgfritt", "or": "eller", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", + "page-maybe-private": "Denne siden er muligens satt til privat. Du kan muligens se den ved å <a href='%s'>logge inn</a>.", + "page-not-found": "Siden finnes ikke", "password": "Passord", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", + "paste-or-dragdrop": "for å limen inn eller dra og slipp bildefil (kun blidefil)", + "participating": "Delta", "preview": "Forhåndsvisning", "previewAttachedImagePopup-title": "Forhåndsvisning", "previewClipboardImagePopup-title": "Forhåndsvisning", "private": "Privat", - "private-desc": "This board is private. Only people added to the board can view and edit it.", + "private-desc": "Denne Tavlen er satt til privat. Kun brukere som er lagt til Tavlen kan se og redigere denne.", "profile": "Profil", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove Cover", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", + "public": "Offentlig", + "public-desc": "Denne Tavlen er Offentlig. Den er synlig for alle med en link og vil vises i søkemotorer som Google. Kun brukere som er lagt til Tavlen kan redigere denne.", + "quick-access-description": "Favorittmerk en Tavle for å legge til en snarvei her", + "remove-cover": "Fjern omslag", + "remove-from-board": "Fjern fra Tavle", + "remove-label": "Fjern Etikett", + "listDeletePopup-title": "Slett Liste?", + "remove-member": "Fjern Medlem", + "remove-member-from-card": "Fjern fra Kort", + "remove-member-pop": "Fjern __name__ (__username__) fra __boardTitle__? Medlemmet vil bli fjernet fra alle kort på denne tavlen. Varsel vil bli sendt til medlemmet.", + "removeMemberPopup-title": "Fjern Medlem?", + "rename": "Omdøp", "rename-board": "Endre navn på tavlen", - "restore": "Restore", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", + "restore": "Gjenopprett", + "save": "Lagre", + "search": "Søk", + "rules": "Regler", + "search-cards": "Søk fra kort og listetitler, beskrivelser og tilpassede felt på denne Tavlen", + "search-example": "Skriv inn søketekst og trykk Enter", + "select-color": "Velg farge", + "select-board": "Velg Tavle", + "set-wip-limit-value": "Sett maksimalt antall oppgaver i denne listen", + "setWipLimitPopup-title": "Sett WIP-begrensning", + "shortcut-assign-self": "Tildel deg selv til dette kortet", + "shortcut-autocomplete-emoji": "Autokompletter emoji", + "shortcut-autocomplete-members": "Autokompletter medlemmer", + "shortcut-clear-filters": "Fjern alle filtre", + "shortcut-close-dialog": "Lukk dialogen", + "shortcut-filter-my-cards": "Filtrer mine kort", + "shortcut-show-shortcuts": "Ta frem denne snarveilisten", + "shortcut-toggle-filterbar": "Aktiver sidefelt for filter", + "shortcut-toggle-searchbar": "Aktiver sidefelt for søk", + "shortcut-toggle-sidebar": "Aktiver sidefelt for Tavler", + "show-cards-minimum-count": "Vis antall kort dersom listen inneholder flere enn", + "sidebar-open": "Åpne sidefelt", + "sidebar-close": "Lukk sidefelt", + "signupPopup-title": "Opprett en Konto", + "star-board-title": "Klikk for å favorittmerke denne Tavlen. Den vil dukke opp øverst i Tavlelisten.", + "starred-boards": "Favorittmerkede Tavler", + "starred-boards-description": "Favorittmerkede Tavler vil dukke opp øverst i Tavlelisten din.", + "subscribe": "Abonner", "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "this-board": "denne Tavlen", + "this-card": "dette Kortet", + "spent-time-hours": "Forbrukt tid (timer)", + "overtime-hours": "Overforbruk tid (timer)", + "overtime": "Overforbruk", + "has-overtime-cards": "Kort med overforbruk", + "has-spenttime-cards": "Kort med forbrukt tid", + "time": "Tid", + "title": "Tittel", + "tracking": "Sporing", + "tracking-info": "Du vil bli varslet om alle endringer på kortene du er du er involvert i som Oppretter eller Medlem.", "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", + "unassign-member": "Fjern tildelt Medlem", + "unsaved-description": "Beskrivelsen er ikke lagret.", + "unwatch": "Fjern overvåkning", + "upload": "Last opp", + "upload-avatar": "Last opp en avatar", + "uploaded-avatar": "Opplastet en avatar", + "custom-top-left-corner-logo-image-url": "Tilpass URL til logo-bilde topp venstre hjørne", + "custom-top-left-corner-logo-link-url": "Tilpass URL til logo-link topp venstre hjørne", + "custom-top-left-corner-logo-height": "Tilpass høyde logo topp venstre hjørne (standard 27 px).", + "custom-login-logo-image-url": "Tilpass URL logo-bilde innlogging", + "custom-login-logo-link-url": "Tilpass URL logo-link innlogging", + "text-below-custom-login-logo": "Tekst under tilpasset logo innlogging", + "automatic-linked-url-schemes": "Egendefinerte URL-tilordninger som automatisk skal kunne klikkes på. Kun én URL-tilordning per linje", "username": "Brukernavn", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", + "import-usernames": "Importer Brukernavn", + "view-it": "Se detaljer", + "warn-list-archived": "advarsel: dette kortet er i en liste i arkivet", + "watch": "Overvåk", + "watching": "Overvåkning", + "watching-info": "Du vil bli varslet om alle endringer på denne Tavlen", + "welcome-board": "Velkomsttavle", + "welcome-swimlane": "Melepel 1", + "welcome-list1": "Basis", + "welcome-list2": "Avansert", + "card-templates-swimlane": "Kortmaler", + "list-templates-swimlane": "List maler", + "board-templates-swimlane": "Tavlemaler", + "what-to-do": "Hva ønsker du å gjøre?", + "wipLimitErrorPopup-title": "Ugyldig WIP-begrensning", + "wipLimitErrorPopup-dialog-pt1": "Antall oppgaver i denne listen er høyere enn WIP-begrensningen som er angitt.", + "wipLimitErrorPopup-dialog-pt2": "Vennligst flytt noen oppgaver ut av denne listen eller sett høyere WIP-begrensning.", + "admin-panel": "Admin-panel", "settings": "Innstillinger", - "people": "Folk", + "people": "Medlemmer", "registration": "Registrering", - "disable-self-registration": "Disable Self-Registration", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", + "disable-self-registration": "Deaktiver selvregistrering", + "invite": "Inviter", + "invite-people": "Invitere medlemmer", + "to-boards": "til Tavle(r)", + "email-addresses": "E-postadresser", + "smtp-host-description": "Adressen til SMTP-serveren som håndterer epostene dine", + "smtp-port-description": "Porten til SMTP-serveren for utgående eposter", + "smtp-tls-description": "Aktiver TLS-support for SMTP-serveren", + "smtp-host": "SMTP-tjener", + "smtp-port": "SMTP-port", "smtp-username": "Brukernavn", "smtp-password": "Passord", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", + "smtp-tls": "TLS-support", + "send-from": "Fra", + "send-smtp-test": "Send en test e-post til deg selv", + "invitation-code": "Invitasjonskode", + "email-invite-register-subject": "__inviter__ sendte en invitasjon til deg", + "email-invite-register-text": "Hei __user__,\n\n__inviter__ har invitert deg til KanBan-tavlen for samarbeid.\n\nVennligst følg lenken under:\n__url__\n\nAktiveringskoden er: __icode__\n\nTakk.", + "email-smtp-test-subject": "SMTP test-e-post", + "email-smtp-test-text": "Vellykket sending av e-post", + "error-invitation-code-not-exist": "Invitasjonskode eksisterer ikke", + "error-notAuthorized": "Du er ikke autorisert til å se denne siden.", + "webhook-title": "Navn Webhook", + "webhook-token": "Token (opsjon for autentifisering)", + "outgoing-webhooks": "Utgående webhook", + "bidirectional-webhooks": "To-veis webhook", + "outgoingWebhooksPopup-title": "Utgående webhook", + "boardCardTitlePopup-title": "Filter korttittel", + "disable-webhook": "Deaktiver denne webhooken", + "global-webhook": "Globale webhooks", + "new-outgoing-webhook": "Ny utgående webhook", + "no-name": "(Ukjent)", + "Node_version": "Versjon node", + "Meteor_version": "Versjon Meteor", + "MongoDB_version": "Versjon MongoDB", + "MongoDB_storage_engine": "Lagringsmotor MongoDB", + "MongoDB_Oplog_enabled": "Aktivert MongoDB Oplog", + "OS_Arch": "OS arkitektur", + "OS_Cpus": "OS CPU-antall", + "OS_Freemem": "OS tilgjengelig minne", + "OS_Loadavg": "OS last gjennomsnitt", + "OS_Platform": "OS plattform", + "OS_Release": "OS utgivelse", + "OS_Totalmem": "OS totalt minne", + "OS_Type": "OS type", + "OS_Uptime": "OS oppetid", + "days": "dager", + "hours": "timer", + "minutes": "minutter", + "seconds": "sekunder", + "show-field-on-card": "Vis dette feltet på kort", + "automatically-field-on-card": "Legg til felt på nye kort", + "always-field-on-card": "Legg til felt på alle kort", + "showLabel-field-on-card": "Vis feltetikett på minikort", + "yes": "Ja", + "no": "Nei", + "accounts": "Konto", + "accounts-allowEmailChange": "Tillat endring e-post", + "accounts-allowUserNameChange": "Tillat endring brukernavn", + "createdAt": "Opprettet på", + "modifiedAt": "Endret kl. ", + "verified": "Verifisert", + "active": "Aktiv", + "card-received": "Mottatt", + "card-received-on": "Opprettet", + "card-end": "Avslutt", + "card-end-on": "Avsluttes", + "editCardReceivedDatePopup-title": "Endre mottatt dato", + "editCardEndDatePopup-title": "Endre sluttdato", + "setCardColorPopup-title": "Sett farge", + "setCardActionsColorPopup-title": "Velg en farge", + "setSwimlaneColorPopup-title": "Velg en farge", + "setListColorPopup-title": "Velg en farge", + "assigned-by": "Tildelt av", + "requested-by": "Forespurt av", + "card-sorting-by-number": "Kort sorter etter nummer", + "board-delete-notice": "Sletting er permanent. Du vil miste alle lister, kort og aksjoner assosiert med denne tavlen,", + "delete-board-confirm-popup": "Alle lister, kort, etiketter og aktiviteter vil bli slettet og du vil ikke kunne gjenopprette innholdet på tavlen. Det er ikke mulig å angre.", + "boardDeletePopup-title": "Slett Tavle?", + "delete-board": "Slett Tavle", + "default-subtasks-board": "Underoppgave for __board__ tavle", + "default": "Standard", + "queue": "Kø", + "subtask-settings": "Innstillinger Underoppgave", + "card-settings": "Innstillinger Kort", + "boardSubtaskSettingsPopup-title": "Innstillinger Tavle Underoppgaver", + "boardCardSettingsPopup-title": "Innstillinger Kort", + "deposit-subtasks-board": "Sett inn Underoppgave på Tavle:", + "deposit-subtasks-list": "Mottakende liste for underoppgaver satt inn her:", + "show-parent-in-minicard": "Vis kilde i minikort", + "prefix-with-full-path": "Prefiks med full sti", + "prefix-with-parent": "Prefiks med kilde", + "subtext-with-full-path": "Undertekst med full sti", + "subtext-with-parent": "Undertekst med kilde", + "change-card-parent": "Endre kortets kilde", + "parent-card": "Kilde kort", + "source-board": "Kilde tavle", + "no-parent": "Ikke vis kilde", + "activity-added-label": "Lagt til etikett '%s' til %s", + "activity-removed-label": "fjernet etikett '%s' fra %s", + "activity-delete-attach": "slettet et vedlegg fra %s", + "activity-added-label-card": "lagt til etikett '%s'", + "activity-removed-label-card": "fjernet etikett '%s'", + "activity-delete-attach-card": "slettet et vedlegg", + "activity-set-customfield": "sett tilpasset felt '%s' til '%s' i %s", + "activity-unset-customfield": "fjernet tilpasset felt '%s' i %s", + "r-rule": "Regel", + "r-add-trigger": "Lagt til utløser", + "r-add-action": "Lagt til aksjon", + "r-board-rules": "Tavle-regler", + "r-add-rule": "Legg til regel", + "r-view-rule": "Se regel", + "r-delete-rule": "Slett regel", + "r-new-rule-name": "Tittel ny regel", + "r-no-rules": "Ingen regler", + "r-trigger": "Utløser", + "r-action": "Aksjon", + "r-when-a-card": "Når et kort", + "r-is": "er", + "r-is-moved": "er flyttet", + "r-added-to": "Lagt til", + "r-removed-from": "Fjernet fra", + "r-the-board": "tavlen", + "r-list": "listen", + "list": "Liste", + "set-filter": "Sett filter", + "r-moved-to": "Flyttet til", + "r-moved-from": "Flyttet fra", + "r-archived": "Flyttet til Arkivet", + "r-unarchived": "Gjenopprettet fra Arkivet", + "r-a-card": "et kort", + "r-when-a-label-is": "Når en etikett er", + "r-when-the-label": "Når en etikett", + "r-list-name": "list navn", + "r-when-a-member": "Når et Medlem er", + "r-when-the-member": "Når Medlemmet", + "r-name": "navn", + "r-when-a-attach": "Når et vedlegg", + "r-when-a-checklist": "Når en sjekkliste er", + "r-when-the-checklist": "Når sjekklisten", + "r-completed": "Gjennomført", + "r-made-incomplete": "Gjennomført ufullstendig", + "r-when-a-item": "Når en sjekklisteoppgave er", + "r-when-the-item": "Når sjekklisteoppgaven", + "r-checked": "Sjekket", + "r-unchecked": "Usjekket", + "r-move-card-to": "Flytt kort til", + "r-top-of": "Toppen av", + "r-bottom-of": "Bunnen av", + "r-its-list": "listen", + "r-archive": "Flytt til Arkiv", + "r-unarchive": "Gjenopprett fra Arkivet", + "r-card": "kort", "r-add": "Legg til", "r-remove": "Fjern", - "r-label": "label", - "r-member": "medlem", + "r-label": "etikett", + "r-member": "Medlem", "r-remove-all": "Fjern alle medlemmer fra kortet", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", + "r-set-color": "Angi farge til", + "r-checklist": "sjekkliste", + "r-check-all": "Merk alle", + "r-uncheck-all": "Fjern merking av alle", + "r-items-check": "Oppgaver i sjekkliste", + "r-check": "Merk", + "r-uncheck": "Fjern merking", + "r-item": "oppgave", + "r-of-checklist": "av sjekkliste", + "r-send-email": "Send en e-post", "r-to": "til", + "r-of": "av", "r-subject": "Emne", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", + "r-rule-details": "Detaljer regel", + "r-d-move-to-top-gen": "Flytt kortet til toppen av kortlisten", + "r-d-move-to-top-spec": "Flytt kort til toppen av liste", "r-d-move-to-bottom-gen": "Flytt kortet til bunnen av sin liste", "r-d-move-to-bottom-spec": "Flytt kortet til bunnen av listen", "r-d-send-email": "Send e-post", "r-d-send-email-to": "til", - "r-d-send-email-subject": "Emne", - "r-d-send-email-message": "Melding", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value.", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", + "r-d-send-email-subject": "emne", + "r-d-send-email-message": "melding", + "r-d-archive": "Flytt kort til arkiv", + "r-d-unarchive": "Gjenopprett kort fra arkiv", + "r-d-add-label": "Legg til etikett", + "r-d-remove-label": "Fjern etikett", + "r-create-card": "Opprett nytt kort", + "r-in-list": "i liste", + "r-in-swimlane": "i svømmebane", + "r-d-add-member": "Legg til Medlem", + "r-d-remove-member": "Fjern Medlem", + "r-d-remove-all-member": "Fjern alle medlemmer", + "r-d-check-all": "Merk alle oppgaver i listen", + "r-d-uncheck-all": "Fjern merking alle oppgaver i listen", + "r-d-check-one": "Merk oppgave", + "r-d-uncheck-one": "Fjern merking oppgave", + "r-d-check-of-list": "av sjekkliste", + "r-d-add-checklist": "Legg til sjekkliste", + "r-d-remove-checklist": "Fjern sjekkliste", + "r-by": "av", + "r-add-checklist": "Legg til sjekkliste", + "r-with-items": "med oppgave", + "r-items-list": "oppgave1,oppgave2,oppgave3", + "r-add-swimlane": "Legg til svømmebane", + "r-swimlane-name": "Navn Svømmebane", + "r-board-note": "Merk: La feltet stå tomt for å treffe alle mulige verdier.", + "r-checklist-note": "Merk: sjekklistens oppgaver må skrives som kommaseparerte verdier.", + "r-when-a-card-is-moved": "Når et kort flyttes til en annen liste", + "r-set": "Sett", + "r-update": "Oppdater", + "r-datefield": "datofelt", "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-df-due-at": "forfall", + "r-df-end-at": "slutt", + "r-df-received-at": "mottatt", + "r-to-current-datetime": "nåværende dato/tid", + "r-remove-value-from": "Fjern verdi fra", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "layout": "Layout", - "hide-logo": "Hide Logo", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "authentication-method": "Autentiseringsmetode", + "authentication-type": "Autentiseringstype", + "custom-product-name": "Tilpasset Produktnavn", + "layout": "Oppsett", + "hide-logo": "Skjul Logo", + "add-custom-html-after-body-start": "Legg til tilpasset HTML etter <body> start", + "add-custom-html-before-body-end": "Legg til tilpasset HTML før </body> slutt", + "error-undefined": "Noe gikk galt", + "error-ldap-login": "En feil oppstod i forbindelse med pålogging", + "display-authentication-method": "Vis autentiseringsmetode", + "default-authentication-method": "Standard autentiseringsmetode", + "duplicate-board": "Dupliser tavle", + "org-number": "Antall Organisasjoner:", + "team-number": "Antall Teams:", + "people-number": "Antall deltagere:", + "swimlaneDeletePopup-title": "Slett Svømmebane?", + "swimlane-delete-pop": "Alle aksjoner vil bli fjernet fra aktivitetstrømmen og du vil ikke kunne gjenopprette svømmebanen. Det er ikke mulig å angre.", + "restore-all": "Gjenopprett alt", + "delete-all": "Slett alt", + "loading": "Laster pågår, vennligst vent.", + "previous_as": "siste tidspunkt var", + "act-a-dueAt": "Endret forfallstid til \nNår: __timeValue__\nHvor: __card__\nForrige forfallstid var __timeOldValue__", + "act-a-endAt": "Endret slutt-tid til __timeValue__ fra (__timeOldValue__)", + "act-a-startAt": "Endret start tid til __timeValue__ fra (__timeOldValue__)", + "act-a-receivedAt": "Endret mottatt-tid til __timeValue__ fra (__timeOldValue__)", + "a-dueAt": "redigert forfallstid til", + "a-endAt": "redigert slutt-tid til", + "a-startAt": "redigert starttid til", + "a-receivedAt": "redigert mottatt tid til", + "almostdue": "Nåværende forfallstid %s nærmer seg", + "pastdue": "Nåværende forfallstid %s er passert", + "duenow": "Nåværende forfallstid %s er i dag", + "act-newDue": "__list__/__card__ har 1. forfallspåminnelse [__board__]", + "act-withDue": "__list__/__card__ forfallspåminnelse [__board__]", + "act-almostdue": "minnet om at nåværende forfallstid (__timeValue__) for __card__ nærmer seg", + "act-pastdue": "minnet om at nåværende forfallstid (__timeValue__) for __card__ er passert", + "act-duenow": "minnet om at nåværende forfallstid (__timeValue__) for __card__ er nå", "act-atUserComment": "Du ble nevnt i [__board__] __list__/__card__", "delete-user-confirm-popup": "Er du sikker på at du vil slette denne kontoen?", - "accounts-allowUserDelete": "Tillat at brukere sletter sin konto", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "delete-team-confirm-popup": "Er du sikker på at du vil slette Teamet? Du kan ikke angre.", + "delete-org-confirm-popup": "Er du sikker på at du vil slette denne Organisasjonen? Du kan ikke angre.", + "accounts-allowUserDelete": "Tillat brukere å slette egen konto", + "hide-minicard-label-text": "Skjul tekst på etikett", + "show-desktop-drag-handles": "Vis ikon for flytting av kort", + "assignee": "Tildelt", + "cardAssigneesPopup-title": "Tildelt", + "addmore-detail": "Legg til detaljert beskrivelse", + "show-on-card": "Vis på Kort", + "new": "Ny", + "editOrgPopup-title": "Rediger Organisasjon", + "newOrgPopup-title": "Ny Organisasjon", + "editTeamPopup-title": "Rediger Team", + "newTeamPopup-title": "Nytt Team", + "editUserPopup-title": "Rediger Bruker", + "newUserPopup-title": "Ny Bruker", + "notifications": "Varsler", + "view-all": "Se Alle", + "filter-by-unread": "Filtrer Uleste", + "mark-all-as-read": "Merk alle som lest", + "remove-all-read": "Fjerne alle leste", + "allow-rename": "Tillat Omdøping", + "allowRenamePopup-title": "Tillat Omdøping", + "start-day-of-week": "Sett første dag i uken", + "monday": "Mandag", + "tuesday": "Tirsdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lørdag", + "sunday": "Søndag", + "status": "Status", + "swimlane": "Svømmebane", + "owner": "Eier", + "last-modified-at": "Siste endret", + "last-activity": "Siste aktivitet", + "voting": "Stemmeavgiving", + "archived": "Arkivert", + "delete-linked-card-before-this-card": "Du kan ikke slette kortet før du har slettet tillinket kort som har", + "delete-linked-cards-before-this-list": "Du kan ikke slette listen før du har slettet kort som er linket til kort i denne listen", + "hide-checked-items": "Skjul utførte oppgaver", + "task": "Oppgave", + "create-task": "Opprett Oppgave", + "ok": "OK", + "organizations": "Organisasjon", + "teams": "Team", + "displayName": "Visningsnavn", + "shortName": "Initialer", + "website": "Nettsted", + "person": "Person", + "my-cards": "Mine Kort", + "card": "Kort", + "board": "Tavle", + "context-separator": "/", + "myCardsSortChange-title": "Sortering mine Kort", + "myCardsSortChangePopup-title": "Sortering mine Kort", + "myCardsSortChange-choice-board": "På Tavle", + "myCardsSortChange-choice-dueat": "På dato Varighet", + "dueCards-title": "Forfalte Kort", + "dueCardsViewChange-title": "Oversikt Forfalte Kort", + "dueCardsViewChangePopup-title": "Oversikt Forfalte Kort", + "dueCardsViewChange-choice-me": "Meg", + "dueCardsViewChange-choice-all": "Alle Brukere", + "dueCardsViewChange-choice-all-description": "Viser alle kort som ikke er ferdigstilt, med \"Forfallsdato\" fra tavler som brukeren har rettigheter til.", + "broken-cards": "Brutte Kort", + "board-title-not-found": "Tavle '%s' ikke funnet.", + "swimlane-title-not-found": "Svømmebane '%s' ikke funnet.", + "list-title-not-found": "Liste '%s' ikke funnet.", + "label-not-found": "Merke '%s' ikke funnet.", + "label-color-not-found": "Farge på Merke %s ikke funnet.", + "user-username-not-found": "Brukernavn '%s' ikke funnet.", + "comment-not-found": "Kort med kommentar som inneholder teksten '%s' ikke funnet.", + "globalSearch-title": "Søk i alle Tavler.", + "no-cards-found": "Ingen Kort funnet.", + "one-card-found": "Ett Kort funnet.", + "n-cards-found": "%s Kort funnet.", + "n-n-of-n-cards-found": "__start__-__end__ av __total__ Kort funnet", + "operator-board": "Tavle", + "operator-board-abbrev": "t", + "operator-swimlane": "svømmebane", + "operator-swimlane-abbrev": "s", + "operator-list": "listen", + "operator-list-abbrev": "l", + "operator-label": "etikett", + "operator-label-abbrev": "#", + "operator-user": "bruker", + "operator-user-abbrev": "@", + "operator-member": "Medlem", + "operator-member-abbrev": "m", + "operator-assignee": "Rettighetstaker", + "operator-assignee-abbrev": "r", + "operator-creator": "oppretter", + "operator-status": "status", + "operator-due": "forfall", + "operator-created": "opprettet", + "operator-modified": "endret", + "operator-sort": "sorter", + "operator-comment": "kommentar", + "operator-has": "har", + "operator-limit": "begens", + "predicate-archived": "arkivert", + "predicate-open": "åpne", + "predicate-ended": "avsluttet", + "predicate-all": "alle", + "predicate-overdue": "forfalt", + "predicate-week": "uke", + "predicate-month": "måned", + "predicate-quarter": "kvartal", + "predicate-year": "år", + "predicate-due": "forfall", + "predicate-modified": "endret", + "predicate-created": "opprettet", + "predicate-attachment": "vedlegg", + "predicate-description": "beskrivelse", + "predicate-checklist": "sjekkliste", + "predicate-start": "start", + "predicate-end": "slutt", + "predicate-assignee": "Rettighetstaker", + "predicate-member": "Medlem", + "predicate-public": "offentlig", + "predicate-private": "privat", + "operator-unknown-error": "%s er ikke en operator", + "operator-number-expected": "operator __operator__ forventet et tall, mottok '__value__'", + "operator-sort-invalid": "sortering av '%s' er ugyldig", + "operator-status-invalid": "'%s' er ikke en gyldig status", + "operator-has-invalid": "%s er ikke en gyldig kontroll av eksistens", + "operator-limit-invalid": "%ser ikke en gyldig begrensning. Begrensning må være et positivt heltall.", + "next-page": "Neste Side", + "previous-page": "Forrige Side", + "heading-notes": "Notater", + "globalSearch-instructions-heading": "Søk etter Instruksjoner", + "globalSearch-instructions-description": "Søk kan inkludere operatorer for å avgrense søket. Operatoren spesifiseres ved å skrive operatorens navn og verdi, atskilt med et kolon. For eksempel vil en spesifisering av operatoren `liste:Blokkert' begrense søket til Kort som er inkludert i en liste med navnet \"Blokkert\". Hvis verdien inneholder mellomrom eller spesialtegn, må den være spesifisert i anførselstegn (f.eks. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Tilgjengelige operatorer:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - kort i tavlene samsvarer med angitt *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - kort i listene stemmer med angitt *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - kort i svømmebaner samsvarer med angitt *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - kort med en kommetar inneholdende *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - kort med merke som samsvarer med *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - forkortelse for `__operator_label__:<color>` eller `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - kort hvor *<username>* ier et *medlemr* or *rettighetstaker*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - forkortelse for bruker:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - kort hvor *<username>* er et *medlem*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - kort hvor *<username>* er en *rettighetstaker*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - kort hvor *<username>* er kortenes oppretter", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - kort som forfaller om *<n>* dager fra nå. `__operator_due__:__predicate_overdue__ lister alle kort med passert forfallsdato.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - kort som er opprettet *<n>* dager siden eller mindre", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - kort som er modifisert *<n>* dager siden eller mindre", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - hvor *<status>* er en av de følgende:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - arkiverte kort", + "globalSearch-instructions-status-all": "`__predicate_all__` - alle arkiverte eller ikke arkiverte kort", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - kort med en sluttdato", + "globalSearch-instructions-status-public": "`__predicate_public__` - kort bare i offentlige tavler", + "globalSearch-instructions-status-private": "`__predicate_private__` - kort bare i private tavler", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - hvor *<field>* er en av `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` eller `__predicate_member__`. Plassering av et `-` foran *<field>* søk i mangel på en verdi i et felt (f.eks. `har:-forfall` søker etter alle kort uten en forfallsdato).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - hvor *<sort-name>* er en av `__predicate_due__`, `__predicate_created__` eller `__predicate_modified__`. For en synkende sortering, plassér en `-` foran navn på sortering.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - hvor*<n>* er et positivt heltall som angir antall kort som skal vises på siden.", + "globalSearch-instructions-notes-1": "Flere operatorer kan angis.", + "globalSearch-instructions-notes-2": "Like operatorer er knyttet sammen med \"ELLER\". Kort som samsvarer med noen av de angitte kriteriene vil returneres.\n`__operator_list__:Tilgjengelig __operator_list__:Blokkert` vil returnere Kort som inneholder lister med navnet \"Blokkert\" eller \"Tilgjengelig\".", + "globalSearch-instructions-notes-3": "Forskjellige operatorer er knyttet sammen med *OG*. Bare kort som samsvarer med alle de angitte kriteriene vil returneres. `__operator_list__:Tilgjengelig __operator_list__:rød` vil kun returnere kort i listen *Tilgjengelig* og som er markert med et *rødt* merke.", + "globalSearch-instructions-notes-3-2": "Dager kan angis som et positivt eller negativt heltall, eller ved bruk av `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` eller `__predicate_year__` for den aktuelle perioden.", + "globalSearch-instructions-notes-4": "Tekstsøk er ikke \"case\"-sensitive.", + "globalSearch-instructions-notes-5": "Som standard er arkiverte kort ikke søkbare, ", + "link-to-search": "Lenke til dette søket", + "excel-font": "Arial", + "number": "Nummer", + "label-colors": "Farge Merke", + "label-names": "Navn på Merke", + "archived-at": "arkivert på", + "sort-cards": "Sortér Kort", + "cardsSortPopup-title": "Sortér Kort", + "due-date": "Forfalldato", + "server-error": "Serverfeil", + "server-error-troubleshooting": "For hjelp til feilsøk ber vi om innsending av feilmelding generert av server.\nFor Snap-installasjon, kjør: `sudo snap logs wekan.wekan`\nFor Docker-installasjon, kjør: `sudo docker logs wekan-app`", + "title-alphabetically": "Tittel (alfabetisk)", + "created-at-newest-first": "Opprettet på (Nyeste Først)", + "created-at-oldest-first": "Opprettet på (Eldste Først)", + "links-heading": "Lenker", + "hide-system-messages-of-all-users": "Skjul systemmeldinger for alle brukere", + "now-system-messages-of-all-users-are-hidden": "Systemmeldinger er nå skjult for alle brukere", + "move-swimlane": "Flytt Svømmebane", + "moveSwimlanePopup-title": "Flytt Svømmebane", + "custom-field-stringtemplate": "Mal Tekststreng", + "custom-field-stringtemplate-format": "Format (bruk %{value} som standardverdi)", + "custom-field-stringtemplate-separator": "Tekstseparator (bruk eller   som separator)", + "custom-field-stringtemplate-item-placeholder": "Trykk Enter for å legge til flere objekt", + "creator": "Oppretter", + "filesReportTitle": "Rapportering Filer", + "orphanedFilesReportTitle": "Rapportering Foreldreløse Filer", + "reports": "Rapportering", + "rulesReportTitle": "Rapportering Regler", + "copy-swimlane": "Kopiér Svømmebane", + "copySwimlanePopup-title": "Kopiér Svømmebane", + "display-card-creator": "Vis etablerer av Kort", + "wait-spinner": "Ventehjul", + "Bounce": "Ventehjul - Hoppende", + "Cube": "Ventehjul - Kubisk", + "Cube-Grid": "Ventehjul - Kubisk med akser", + "Dot": "Ventehjul - Prikk", + "Double-Bounce": "Ventehjul - Dobbelt - Hoppende", + "Rotateplane": "Ventehjul - Rotasjonsplan", + "Scaleout": "Ventehjul - Utskaliering", + "Wave": "Ventehjul - Bølge", + "maximize-card": "Maksimer Kort", + "minimize-card": "Minimer Kort", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/nl.i18n.json b/i18n/nl.i18n.json index 47eca655f..583c49ee7 100644 --- a/i18n/nl.i18n.json +++ b/i18n/nl.i18n.json @@ -1,769 +1,1062 @@ { - "accept": "Accepteren", - "act-activity-notify": "Activiteiten Notificatie", - "act-addAttachment": "heeft bijlage __attachment__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-deleteAttachment": "heeft bijlage __attachment__ verwijderd op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-addSubtask": "heeft subtaak __subtask__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-addLabel": "heeft label __label__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-addedLabel": "heeft label __label__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-removeLabel": "heeft label __label__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-removedLabel": "heeft label __label__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-addChecklist": "heeft checklist __checklist__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-addChecklistItem": "heeft checklist item __checklistItem__ toegevoegd aan checklist __checklist__ op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-removeChecklist": "heeft checklist __checklist__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-removeChecklistItem": "heeft checklist item __checklistItem__ verwijderd van checklist __checkList__ op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-checkedItem": "heeft __checklistItem__ aangevinkt van checklist __checklist__ op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-uncheckedItem": "heeft __checklistItem__ uitgevinkt van checklist __checklist__ op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-completeChecklist": "heeft checklist __checklist__ afgewerkt op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-uncompleteChecklist": "heeft checklist __checklist__ onafgewerkt op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-addComment": "heeft aantekening toegevoegd aan kaart __card__: __comment__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-editComment": "heeft aantekening gewijzigd op kaart __card__: __comment__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-deleteComment": "heeft aantekening verwijderd van kaart __card__: __comment__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-createBoard": "bord __board__ aangemaakt", - "act-createSwimlane": "swimlane __swimlane__ aangemaakt op bord __board__", - "act-createCard": "heeft kaart __card__ aangemaakt in lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-createCustomField": "heeft maatwerkveld __customField__ aangemaakt op bord __board__", - "act-deleteCustomField": "heeft maatwerkveld __customField__ verwijderd van bord __board__", - "act-setCustomField": "heeft maatwerkveld gewijzigd __customField__: __customFieldValue__ op kaart __card__ in lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-createList": "lijst __list__ toegevoegd aan bord __board__", - "act-addBoardMember": "heeft lid __member__ toegevoegd aan bord __board__", - "act-archivedBoard": "Bord __board__ verplaatst naar Archief", - "act-archivedCard": "heeft kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__ verplaatst naar Archief", - "act-archivedList": "Lijst __list__ uit swimlane __swimlane__ op bord __board__ verplaatst naar Archief", - "act-archivedSwimlane": "Swimlane __swimlane__ op bord __board__ verplaatst naar Archief", - "act-importBoard": "bord __board__ geïmporteerd", - "act-importCard": "heeft kaart __card__ geïmporteerd in lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-importList": "lijst __list__ geïmporteerd in swimlane __swimlane__ op bord __board__", - "act-joinMember": "heeft lid __member__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-moveCard": "heeft kaart __card__ verplaatst op bord __board__ van lijst __oldList__ uit swimlane __oldSwimlane__ naar lijst __list__ in swimlane __swimlane__", - "act-moveCardToOtherBoard": "heeft kaart __card__ verplaatst van lijst __oldList__ uit swimlane __oldSwimlane__ op bord __oldBoard__ naar lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-removeBoardMember": "heeft lid __member__ verwijderd van bord __board__", - "act-restoredCard": "heeft kaart __card__ teruggehaald naar lijst __list__ in swimlane __swimlane__ op bord __board__", - "act-unjoinMember": "heeft lid __member__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Acties", - "activities": "Activiteiten", - "activity": "Activiteit", - "activity-added": "%s toegevoegd aan %s", - "activity-archived": "%s verplaatst naar Archief", - "activity-attached": "%s bijgevoegd aan %s", - "activity-created": "%s aangemaakt", - "activity-customfield-created": "maatwerkveld aangemaakt %s", - "activity-excluded": "%s uitgesloten van %s", - "activity-imported": "%s geïmporteerd in %s van %s", - "activity-imported-board": "%s geïmporteerd van %s", - "activity-joined": "%s toegetreden", - "activity-moved": "%s verplaatst van %s naar %s", - "activity-on": "bij %s", - "activity-removed": "%s verwijderd van %s", - "activity-sent": "%s gestuurd naar %s", - "activity-unjoined": "uit %s gegaan", - "activity-subtask-added": "subtaak toegevoegd aan %s", - "activity-checked-item": "%s aangevinkt in checklist %s van %s", - "activity-unchecked-item": "%s uitgevinkt in checklist %s van %s", - "activity-checklist-added": "checklist toegevoegd aan %s", - "activity-checklist-removed": "checklist verwijderd van %s", - "activity-checklist-completed": "checklist %s afgewerkt van %s", - "activity-checklist-uncompleted": "checklist %s onafgewerkt van %s", - "activity-checklist-item-added": "checklist item toegevoegd aan '%s' in '%s'", - "activity-checklist-item-removed": "checklist item verwijderd van '%s' in %s", - "add": "Toevoegen", - "activity-checked-item-card": "%s aangevinkt in checklist %s", - "activity-unchecked-item-card": "%s uitgevinkt in checklist %s", - "activity-checklist-completed-card": "heeft checklist __checklist__ afgewerkt op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", - "activity-checklist-uncompleted-card": "checklist %s onafgewerkt", - "activity-editComment": "aantekening gewijzigd %s", - "activity-deleteComment": "aantekening verwijderd %s", - "add-attachment": "Bijlage Toevoegen", - "add-board": "Bord Toevoegen", - "add-card": "Kaart Toevoegen", - "add-swimlane": "Swimlane Toevoegen", - "add-subtask": "Subtaak Toevoegen", - "add-checklist": "Checkcklist Toevoegen", - "add-checklist-item": "Voeg item toe aan checklist", - "add-cover": "Cover Toevoegen", - "add-label": "Label Toevoegen", - "add-list": "Lijst Toevoegen", - "add-members": "Leden Toevoegen", - "added": "Toegevoegd", - "addMemberPopup-title": "Leden", - "admin": "Administrator", - "admin-desc": "Kan kaarten bekijken en wijzigen, leden verwijderen, en instellingen voor het bord aanpassen.", - "admin-announcement": "Melding", - "admin-announcement-active": "Systeem melding", - "admin-announcement-title": "Melding van de administrator", - "all-boards": "Alle borden", - "and-n-other-card": "En __count__ andere kaarten", - "and-n-other-card_plural": "En __count__ andere kaarten", - "apply": "Aanmelden", - "app-is-offline": "Wekan is aan het laden, wacht alstublieft. Het verversen van de pagina zorgt voor verlies van gegevens. Als Wekan niet laadt, check dan of de Wekan server niet is gestopt. ", - "archive": "Verplaats naar Archief", - "archive-all": "Verplaats Alles naar Archief", - "archive-board": "Verplaats Bord naar Archief", - "archive-card": "Verplaats Kaart naar Archief", - "archive-list": "Verplaats Lijst naar Archief", - "archive-swimlane": "Verplaats Swimlane naar Archief", - "archive-selection": "Verplaats selectie naar Archief", - "archiveBoardPopup-title": "Bord naar Archief verplaatsen?", - "archived-items": "Archiveren", - "archived-boards": "Borden in Archief", - "restore-board": "Herstel Bord", - "no-archived-boards": "Geen Borden in Archief.", - "archives": "Archief", - "template": "Template", - "templates": "Templates", - "assign-member": "Lid toevoegen", - "attached": "bijgevoegd", - "attachment": "Bijlage", - "attachment-delete-pop": "Een bijlage verwijderen is permanent. Er is geen herstelmogelijkheid.", - "attachmentDeletePopup-title": "Bijlage verwijderen?", - "attachments": "Bijlagen", - "auto-watch": "Automatisch borden bekijken wanneer deze aangemaakt worden", - "avatar-too-big": "De bestandsgrootte van je avatar is te groot (70KB max)", - "back": "Terug", - "board-change-color": "Wijzig kleur", - "board-nb-stars": "%s sterren", - "board-not-found": "Bord is niet gevonden", - "board-private-info": "Dit bord is nu <strong>privé</strong>.", - "board-public-info": "Dit bord is nu <strong>openbaar</strong>.", - "boardChangeColorPopup-title": "Verander achtergrond van bord", - "boardChangeTitlePopup-title": "Hernoem bord", - "boardChangeVisibilityPopup-title": "Verander zichtbaarheid", - "boardChangeWatchPopup-title": "Verander naar 'Watch'", - "boardMenuPopup-title": "Bord Instellingen", - "boardChangeViewPopup-title": "Bord overzicht", - "boards": "Borden", - "board-view": "Bord overzicht", - "board-view-cal": "Kalender", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Klap in", - "board-view-lists": "Lijsten", - "bucket-example": "Zoals bijvoorbeeld een \"Bucket List\"", - "cancel": "Annuleren", - "card-archived": "Deze kaart is verplaatst naar Archief.", - "board-archived": "Dit bord is verplaatst naar Archief.", - "card-comments-title": "Deze kaart heeft %s aantekening(en).", - "card-delete-notice": "Verwijdering is permanent. Als je dit doet, verlies je alle informatie die op deze kaart is opgeslagen.", - "card-delete-pop": "Alle acties worden verwijderd van de activiteiten feed, en er zal geen mogelijkheid zijn om de kaart opnieuw te openen. Er is geen herstelmogelijkheid.", - "card-delete-suggest-archive": "Je kunt een kaart naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", - "card-due": "Verval", - "card-due-on": "Vervalt op ", - "card-spent": "Gespendeerde tijd", - "card-edit-attachments": "Wijzig bijlagen", - "card-edit-custom-fields": "Wijzig maatwerkvelden", - "card-edit-labels": "Wijzig labels", - "card-edit-members": "Wijzig leden", - "card-labels-title": "Wijzig de labels van de kaart.", - "card-members-title": "Voeg of verwijder leden van het bord toe aan de kaart.", - "card-start": "Begin", - "card-start-on": "Begint op", - "cardAttachmentsPopup-title": "Voeg bestand toe vanuit", - "cardCustomField-datePopup-title": "Wijzigingsdatum", - "cardCustomFieldsPopup-title": "Wijzig maatwerkvelden", - "cardDeletePopup-title": "Kaart verwijderen?", - "cardDetailsActionsPopup-title": "Kaart actie ondernemen", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Leden", - "cardMorePopup-title": "Meer", - "cardTemplatePopup-title": "Template aanmaken", - "cards": "Kaarten", - "cards-count": "Kaarten", - "casSignIn": "Log in met CAS", - "cardType-card": "Kaart", - "cardType-linkedCard": "Gekoppelde Kaart", - "cardType-linkedBoard": "Gekoppeld Bord", - "change": "Wijzig", - "change-avatar": "Wijzig avatar", - "change-password": "Wijzig wachtwoord", - "change-permissions": "Wijzig permissies", - "change-settings": "Wijzig instellingen", - "changeAvatarPopup-title": "Wijzig avatar", - "changeLanguagePopup-title": "Wijzig taal", - "changePasswordPopup-title": "Wijzig wachtwoord", - "changePermissionsPopup-title": "Wijzig permissies", - "changeSettingsPopup-title": "Wijzig instellingen", - "subtasks": "Subtaken", - "checklists": "Checklists", - "click-to-star": "Klik om het bord als favoriet in te stellen", - "click-to-unstar": "Klik om het bord uit favorieten weg te halen", - "clipboard": "Vanuit clipboard of sleep het bestand hierheen", - "close": "Sluiten", - "close-board": "Sluit bord", - "close-board-pop": "Je kunt het bord terughalen door de \"Archief\" knop te klikken in de menubalk \"Mijn Borden\".", - "color-black": "zwart", - "color-blue": "blauw", - "color-crimson": "karmijn", - "color-darkgreen": "donkergroen", - "color-gold": "goud", - "color-gray": "grijs", - "color-green": "groen", - "color-indigo": "indigo", - "color-lime": "felgroen", - "color-magenta": "magenta", - "color-mistyrose": "zachtroze", - "color-navy": "marineblauw", - "color-orange": "oranje", - "color-paleturquoise": "vaalturkoois", - "color-peachpuff": "perzikroze", - "color-pink": "roze", - "color-plum": "pruim", - "color-purple": "paars", - "color-red": "rood", - "color-saddlebrown": "zadelbruin", - "color-silver": "zilver", - "color-sky": "lucht", - "color-slateblue": "leiblauw", - "color-white": "wit", - "color-yellow": "geel", - "unset-color": "Ongedefinieerd", - "comment": "Aantekening", - "comment-placeholder": "Schrijf aantekening", - "comment-only": "Alleen aantekeningen maken", - "comment-only-desc": "Kan alleen op kaarten aantekenen.", - "no-comments": "Geen aantekeningen", - "no-comments-desc": "Zie geen aantekeningen of activiteiten.", - "worker": "Medewerker", - "worker-desc": "Kan alleen kaarten verplaatsen, zichzelf aan kaarten koppelen en aantekeningen maken.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Weet je zeker dat je de subtaak wilt verwijderen?", - "confirm-checklist-delete-dialog": "Weet je zeker dat je de checklist wilt verwijderen?", - "copy-card-link-to-clipboard": "Kopieer kaart link naar klembord", - "linkCardPopup-title": "Koppel Kaart", - "searchElementPopup-title": "Zoek", - "copyCardPopup-title": "Kopieer kaart", - "copyChecklistToManyCardsPopup-title": "Checklist sjabloon kopiëren naar meerdere kaarten", - "copyChecklistToManyCardsPopup-instructions": "Doel kaart titels en omschrijvingen in dit JSON formaat", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titel eerste kaart\", \"description\":\"Omschrijving eerste kaart\"}, {\"title\":\"Titel tweede kaart\",\"description\":\"Omschrijving tweede kaart\"},{\"title\":\"Titel laatste kaart\",\"description\":\"Omschrijving laatste kaart\"} ]", - "create": "Aanmaken", - "createBoardPopup-title": "Bord aanmaken", - "chooseBoardSourcePopup-title": "Importeer bord", - "createLabelPopup-title": "Label aanmaken", - "createCustomField": "Veld aanmaken", - "createCustomFieldPopup-title": "Veld aanmaken", - "current": "Huidige", - "custom-field-delete-pop": "Er is geen herstelmogelijkheid. Deze actie zal dit maatwerkveld van alle kaarten verwijderen en de bijbehorende historie wissen.", - "custom-field-checkbox": "Checkbox", - "custom-field-date": "Datum", - "custom-field-dropdown": "Dropdown Lijst", - "custom-field-dropdown-none": "(geen)", - "custom-field-dropdown-options": "Lijst Opties", - "custom-field-dropdown-options-placeholder": "Toets Enter om meer opties toe te voegen ", - "custom-field-dropdown-unknown": "(onbekend)", - "custom-field-number": "Aantal", - "custom-field-text": "Tekst", - "custom-fields": "Maatwerkvelden", - "date": "Datum", - "decline": "Weigeren", - "default-avatar": "Standaard avatar", - "delete": "Verwijderen", - "deleteCustomFieldPopup-title": "Maatwerkveld verwijderen?", - "deleteLabelPopup-title": "Label verwijderen?", - "description": "Beschrijving", - "disambiguateMultiLabelPopup-title": "Disambigueer Label Actie", - "disambiguateMultiMemberPopup-title": "Disambigueer Lid Actie", - "discard": "Negeer", - "done": "Klaar", - "download": "Download", - "edit": "Wijzig", - "edit-avatar": "Wijzig avatar", - "edit-profile": "Wijzig profiel", - "edit-wip-limit": "Verander WIP limiet", - "soft-wip-limit": "Zachte WIP limiet", - "editCardStartDatePopup-title": "Wijzig start datum", - "editCardDueDatePopup-title": "Wijzig vervaldatum", - "editCustomFieldPopup-title": "Wijzig Veld", - "editCardSpentTimePopup-title": "Verander gespendeerde tijd", - "editLabelPopup-title": "Wijzig label", - "editNotificationPopup-title": "Wijzig notificatie", - "editProfilePopup-title": "Wijzig profiel", - "email": "E-mail", - "email-enrollAccount-subject": "Er is een account voor je aangemaakt op __siteName__", - "email-enrollAccount-text": "Hallo __user__,\n\nOm gebruik te maken van de online dienst, kan je op de volgende link klikken.\n\n__url__\n\nBedankt.", - "email-fail": "E-mail verzenden is mislukt", - "email-fail-text": "Fout tijdens het verzenden van de email", - "email-invalid": "Ongeldig e-mailadres", - "email-invite": "Nodig uit via e-mail", - "email-invite-subject": "__inviter__ heeft je een uitnodiging gestuurd", - "email-invite-text": "Beste __user__,\n\n__inviter__ heeft je uitgenodigd om voor een samenwerking deel te nemen aan het bord \"__board__\".\n\nKlik op de link hieronder:\n\n__url__\n\nBedankt.", - "email-resetPassword-subject": "Reset je wachtwoord op __siteName__", - "email-resetPassword-text": "Hallo __user__,\n\nKlik op de link hier beneden om je wachtwoord te resetten.\n\n__url__\n\nBedankt.", - "email-sent": "E-mail is verzonden", - "email-verifyEmail-subject": "Verifieer je e-mailadres op __siteName__", - "email-verifyEmail-text": "Hallo __user__,\n\nOm je e-mail te verifiëren vragen we je om op de link hieronder te klikken.\n\n__url__\n\nBedankt.", - "enable-wip-limit": "Activeer WIP limiet", - "error-board-doesNotExist": "Dit bord bestaat niet.", - "error-board-notAdmin": "Je moet een administrator zijn van dit bord om dat te doen.", - "error-board-notAMember": "Je moet een lid zijn van dit bord om dat te doen.", - "error-json-malformed": "JSON format klopt niet", - "error-json-schema": "De JSON data bevat niet de juiste informatie in de juiste format", - "error-list-doesNotExist": "Deze lijst bestaat niet", - "error-user-doesNotExist": "Deze gebruiker bestaat niet", - "error-user-notAllowSelf": "Je kan jezelf niet uitnodigen", - "error-user-notCreated": "Deze gebruiker is niet aangemaakt", - "error-username-taken": "Deze gebruikersnaam is al in gebruik", - "error-email-taken": "Dit e-mailadres is al in gebruik", - "export-board": "Exporteer bord", - "sort": "Sorteer", - "sort-desc": "Klik om lijst te sorteren", - "list-sort-by": "Sorteer lijst op", - "list-label-modifiedAt": "Laatste toegangstijd", - "list-label-title": "Naam van de lijst", - "list-label-sort": "Je handmatige volgorde", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter kaarten of lijsten", - "list-filter-label": "Filter lijst op titel", - "filter-clear": "Wis filter", - "filter-no-label": "Geen label", - "filter-no-member": "Geen lid", - "filter-no-custom-fields": "Geen maatwerkvelden", - "filter-show-archive": "Toon gearchiveerde lijsten", - "filter-hide-empty": "Verberg lege lijsten", - "filter-on": "Filter is actief", - "filter-on-desc": "Je bent nu kaarten aan het filteren op dit bord. Klik hier om het filter te wijzigen.", - "filter-to-selection": "Filter zoals selectie", - "advanced-filter-label": "Geavanceerd Filter", - "advanced-filter-description": "Met het Geavanceerd Filter kun je een tekst schrijven die de volgende operatoren mag bevatten: == != <= >= && || ( ) Een Spatie wordt als scheiding gebruikt tussen de verschillende operatoren. Je kunt filteren op alle Maatwerkvelden door hun namen en waarden in te tikken. Bijvoorbeeld: Veld1 == Waarde1. Let op: Als velden of waarden spaties bevatten dan moet je die tussen enkele aanhalingstekens zetten. Bijvoorbeeld: 'Veld 1' == 'Waarde 1'. Om controle karakters (' \\/) over te slaan gebruik je \\. Bijvoorbeeld: Veld1 == I\\'m. Je kunt ook meerdere condities combineren. Bijvoorbeeld: F1 == V1 || F1 == V2. Normalerwijze worden alle operatoren van links naar rechts verwerkt. Dit kun je veranderen door ronde haken te gebruiken. Bijvoorbeeld: F1 == V1 && ( F2 == V2 || F2 == V3 ). Je kunt ook met regex in tekstvelden zoeken. Bijvoorbeeld: F1 == /Tes.*/i", - "fullname": "Volledige naam", - "header-logo-title": "Ga terug naar jouw borden pagina.", - "hide-system-messages": "Verberg systeemberichten", - "headerBarCreateBoardPopup-title": "Bord aanmaken", - "home": "Voorpagina", - "import": "Importeer", - "link": "Link", - "import-board": "Importeer bord", - "import-board-c": "Importeer bord", - "import-board-title-trello": "Importeer bord vanuit Trello", - "import-board-title-wekan": "Importeer bord vanuit eerdere export", - "import-sandstorm-backup-warning": "Verwijder nog niet de data van je geëxporteerde Trello-bord totdat je vastgesteld hebt dat het Wekan-bord werkt. Doe dit door het nieuwe bord te sluiten en opnieuw te openen. Als er dan een foutmelding krijgt of het nieuwe bord opent niet dan kun je nog terugvallen op het originele bord. ", - "import-sandstorm-warning": "Het geïmporteerde bord verwijdert alle huidige data op dit bord, om het daarna te vervangen.", - "from-trello": "Vanuit Trello", - "from-wekan": "Vanuit eerdere export", - "import-board-instruction-trello": "Op jouw Trello bord, ga naar 'Menu', dan naar 'Meer', 'Print en Exporteer', 'Exporteer JSON', en kopieer de tekst.", - "import-board-instruction-wekan": "Ga op je bord naar 'Menu' en klik dan 'Export board' en kopieer de tekst in het gedownloade bestand.", - "import-board-instruction-about-errors": "Als je tijdens de import van het bord foutmeldingen krijgt is de import soms toch gelukt en vind je het bord terug op de 'Alle borden' scherm.", - "import-json-placeholder": "Plak geldige JSON data hier", - "import-map-members": "Breng leden in kaart", - "import-members-map": "Je geïmporteerde bord heeft een aantal leden. Koppel de leden die je wilt importeren aan je gebruikers.", - "import-show-user-mapping": "Breng leden overzicht tevoorschijn", - "import-user-select": "Kies een bestaande gebruiker die je als dit lid wilt koppelen", - "importMapMembersAddPopup-title": "Kies lid", - "info": "Versie", - "initials": "Initialen", - "invalid-date": "Ongeldige datum", - "invalid-time": "Ongeldige tijd", - "invalid-user": "Ongeldige gebruiker", - "joined": "doet nu mee met", - "just-invited": "Je bent zojuist uitgenodigd om mee toen doen aan dit bord", - "keyboard-shortcuts": "Toetsenbord snelkoppelingen", - "label-create": "Label aanmaken", - "label-default": "%s label (standaard)", - "label-delete-pop": "Er is geen herstelmogelijkheid. Deze actie zal het label van alle kaarten verwijderen met de bijbehorende historie.", - "labels": "Labels", - "language": "Taal", - "last-admin-desc": "Je kunt de permissies niet veranderen omdat er minimaal een administrator moet zijn.", - "leave-board": "Verlaat bord", - "leave-board-pop": "Weet je zeker dat je __boardTitle__ wilt verlaten? Je wordt verwijderd van alle kaarten binnen dit bord", - "leaveBoardPopup-title": "Bord verlaten?", - "link-card": "Link naar deze kaart", - "list-archive-cards": "Verplaats alle kaarten in deze lijst naar Archief", - "list-archive-cards-pop": "Dit zal alle kaarten uit deze lijst op dit bord verwijderen. Om de kaarten in het Archief te tonen en terug te halen, klik \"Menu\" > \"Archief\".", - "list-move-cards": "Verplaats alle kaarten in deze lijst", - "list-select-cards": "Selecteer alle kaarten in deze lijst", - "set-color-list": "Wijzig kleur in", - "listActionPopup-title": "Lijst acties", - "swimlaneActionPopup-title": "Swimlane handelingen", - "swimlaneAddPopup-title": "Voeg hieronder een Swimlane toe", - "listImportCardPopup-title": "Importeer een Trello kaart", - "listMorePopup-title": "Meer", - "link-list": "Link naar deze lijst", - "list-delete-pop": "Alle acties zullen verwijderd worden van de activiteiten feed, en je zult deze niet meer kunnen herstellen. Er is geen herstelmogelijkheid.", - "list-delete-suggest-archive": "Je kunt een lijst naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", - "lists": "Lijsten", - "swimlanes": "Swimlanes", - "log-out": "Uitloggen", - "log-in": "Inloggen", - "loginPopup-title": "Inloggen", - "memberMenuPopup-title": "Instellingen van leden", - "members": "Leden", - "menu": "Menu", - "move-selection": "Verplaats selectie", - "moveCardPopup-title": "Verplaats kaart", - "moveCardToBottom-title": "Verplaats naar beneden", - "moveCardToTop-title": "Verplaats naar boven", - "moveSelectionPopup-title": "Verplaats selectie", - "multi-selection": "Multi-selectie", - "multi-selection-on": "Multi-selectie staat aan", - "muted": "Stil", - "muted-info": "Je zal nooit meer geïnformeerd worden bij veranderingen in dit bord.", - "my-boards": "Mijn Borden", - "name": "Naam", - "no-archived-cards": "Geen kaarten in Archief.", - "no-archived-lists": "Geen lijsten in Archief..", - "no-archived-swimlanes": "Geen swimlanes in Archief.", - "no-results": "Geen resultaten", - "normal": "Normaal", - "normal-desc": "Kan de kaarten zien en wijzigen. Kan de instellingen niet wijzigen.", - "not-accepted-yet": "Uitnodiging nog niet geaccepteerd", - "notify-participate": "Ontvang updates van elke kaart die je hebt aangemaakt of lid van bent", - "notify-watch": "Ontvang updates van elke bord, lijst of kaart die je bekijkt.", - "optional": "optioneel", - "or": "of", - "page-maybe-private": "Deze pagina is privé. Je kan het bekijken door <a href='%s'>in te loggen</a>.", - "page-not-found": "Pagina niet gevonden.", - "password": "Wachtwoord", - "paste-or-dragdrop": "Om te plakken, of slepen & neer te laten (alleen afbeeldingen)", - "participating": "Deelnemen", - "preview": "Voorbeeld", - "previewAttachedImagePopup-title": "Voorbeeld", - "previewClipboardImagePopup-title": "Voorbeeld", - "private": "Privé", - "private-desc": "Dit bord is privé. Alleen gebruikers die toegevoegd zijn aan het bord kunnen het bekijken en wijzigen.", - "profile": "Profiel", - "public": "Openbaar", - "public-desc": "Dit bord is openbaar. Het is zichtbaar voor iedereen met de link en zal tevoorschijn komen op zoekmachines zoals Google. Alleen gebruikers die toegevoegd zijn aan het bord kunnen het wijzigen.", - "quick-access-description": "Maak een bord favoriet om een snelkoppeling toe te voegen aan deze balk.", - "remove-cover": "Verwijder Cover", - "remove-from-board": "Verwijder van bord", - "remove-label": "Verwijder label", - "listDeletePopup-title": "Lijst verwijderen?", - "remove-member": "Verwijder lid", - "remove-member-from-card": "Verwijder van kaart", - "remove-member-pop": "Verwijder __name__ (__username__) van __boardTitle__? Het lid zal verwijderd worden van alle kaarten op dit bord, en zal een notificatie ontvangen.", - "removeMemberPopup-title": "Lid verwijderen?", - "rename": "Hernoem", - "rename-board": "Hernoem bord", - "restore": "Herstel", - "save": "Opslaan", - "search": "Zoek", - "rules": "Regels", - "search-cards": "Zoek in kaart/lijst, titels, beschrijvingen en maatwerkvelden op dit bord", - "search-example": "Tekst om naar te zoeken?", - "select-color": "Selecteer kleur", - "set-wip-limit-value": "Zet een limiet voor het maximaal aantal taken in deze lijst", - "setWipLimitPopup-title": "Zet een WIP limiet", - "shortcut-assign-self": "Voeg jezelf toe aan huidige kaart", - "shortcut-autocomplete-emoji": "Emojis automatisch aanvullen", - "shortcut-autocomplete-members": "Leden automatisch aanvullen", - "shortcut-clear-filters": "Wis alle filters", - "shortcut-close-dialog": "Sluit dialoog", - "shortcut-filter-my-cards": "Filter mijn kaarten", - "shortcut-show-shortcuts": "Haal lijst met snelkoppelingen tevoorschijn", - "shortcut-toggle-filterbar": "Schakel Filter Zijbalk", - "shortcut-toggle-sidebar": "Schakel Bord Zijbalk", - "show-cards-minimum-count": "Laat het aantal kaarten zien wanneer de lijst meer kaarten heeft dan", - "sidebar-open": "Open Zijbalk", - "sidebar-close": "Sluit Zijbalk", - "signupPopup-title": "Maak een account aan", - "star-board-title": "Klik om het bord toe te voegen aan favorieten, waarna hij aan de bovenbalk tevoorschijn komt.", - "starred-boards": "Favoriete Borden", - "starred-boards-description": "Favoriete borden komen tevoorschijn aan de bovenbalk.", - "subscribe": "Abonneer", - "team": "Team", - "this-board": "dit bord", - "this-card": "deze kaart", - "spent-time-hours": "Gespendeerde tijd (in uren)", - "overtime-hours": "Overwerk (in uren)", - "overtime": "Overwerk", - "has-overtime-cards": "Heeft kaarten met overwerk", - "has-spenttime-cards": "Heeft tijd besteed aan kaarten", - "time": "Tijd", - "title": "Titel", - "tracking": "Volgen", - "tracking-info": "Je wordt op de hoogte gesteld als er veranderingen zijn aan de kaarten waar je lid of maker van bent.", - "type": "Type", - "unassign-member": "Lid verwijderen", - "unsaved-description": "Je hebt een niet opgeslagen beschrijving.", - "unwatch": "Niet bekijken", - "upload": "Upload", - "upload-avatar": "Upload een avatar", - "uploaded-avatar": "Avatar is geüpload", - "username": "Gebruikersnaam", - "view-it": "Bekijk het", - "warn-list-archived": "Let op: deze kaart zit in gearchiveerde lijst", - "watch": "Bekijk", - "watching": "Bekijken", - "watching-info": "Je zal op de hoogte worden gesteld als er een verandering plaatsvind op dit bord.", - "welcome-board": "Welkom Bord", - "welcome-swimlane": "Mijlpaal 1", - "welcome-list1": "Basis", - "welcome-list2": "Geadvanceerd", - "card-templates-swimlane": "Kaart Templates", - "list-templates-swimlane": "Lijst Templates", - "board-templates-swimlane": "Bord Templates", - "what-to-do": "Wat wil je doen?", - "wipLimitErrorPopup-title": "Ongeldige WIP limiet", - "wipLimitErrorPopup-dialog-pt1": "Het aantal taken in deze lijst is groter dan de gedefinieerde WIP limiet ", - "wipLimitErrorPopup-dialog-pt2": "Verwijder een aantal taken uit deze lijst, of zet de WIP limiet hoger", - "admin-panel": "Administrator paneel", - "settings": "Instellingen", - "people": "Gebruikers", - "registration": "Registratie", - "disable-self-registration": "Schakel zelf-registratie uit", - "invite": "Uitnodigen", - "invite-people": "Nodig mensen uit", - "to-boards": "Voor bord(en)", - "email-addresses": "E-mailadressen", - "smtp-host-description": "Het adres van de SMTP server die e-mails zal versturen.", - "smtp-port-description": "De poort van de SMTP server wordt gebruikt voor uitgaande e-mails.", - "smtp-tls-description": "Gebruik TLS ondersteuning voor SMTP server.", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Poort", - "smtp-username": "Gebruikersnaam", - "smtp-password": "Wachtwoord", - "smtp-tls": "TLS ondersteuning", - "send-from": "Van", - "send-smtp-test": "Verzend een test email naar jezelf", - "invitation-code": "Uitnodigings code", - "email-invite-register-subject": "__inviter__ heeft je een uitnodiging gestuurd", - "email-invite-register-text": "Beste __user__,\n\n__inviter__ nodigt je uit voor een Kanban-bord om samen te werken.\n\nHet bord vind je via onderstaande link:\n__url__\n\nJe uitnodigingscode is: __icode__\n\nBedankt en tot ziens.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "Je hebt met succes een email verzonden", - "error-invitation-code-not-exist": "Uitnodigings code bestaat niet", - "error-notAuthorized": "Je bent niet toegestaan om deze pagina te bekijken.", - "webhook-title": "Webhook Naam", - "webhook-token": "Token (Optioneel voor Authenticatie)", - "outgoing-webhooks": "Uitgaande Webhooks", - "bidirectional-webhooks": "Twee-Weg Webhooks", - "outgoingWebhooksPopup-title": "Uitgaande Webhooks", - "boardCardTitlePopup-title": "Kaarttitel Filter", - "disable-webhook": "Schakel deze Webhook uit", - "global-webhook": "Globale Webhooks", - "new-outgoing-webhook": "Nieuwe webhook", - "no-name": "(Onbekend)", - "Node_version": "Node versie", - "Meteor_version": "Meteor versie", - "MongoDB_version": "MongoDB versie", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog ingeschakeld", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Vrij Geheugen", - "OS_Loadavg": "OS Gemiddelde Belasting", - "OS_Platform": "OS Platform", - "OS_Release": "OS Versie", - "OS_Totalmem": "OS Totaal Geheugen", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "dagen", - "hours": "uren", - "minutes": "minuten", - "seconds": "seconden", - "show-field-on-card": "Toon dit veld op kaart", - "automatically-field-on-card": "Maak veld automatisch aan op alle kaarten", - "showLabel-field-on-card": "Toon veldnaam op minikaart", - "yes": "Ja", - "no": "Nee", - "accounts": "Accounts", - "accounts-allowEmailChange": "Sta E-mailadres wijzigingen toe", - "accounts-allowUserNameChange": "Sta Gebruikersnaam wijzigingen toe", - "createdAt": "Gemaakt op", - "verified": "Geverifieerd", - "active": "Actief", - "card-received": "Ontvangen", - "card-received-on": "Ontvangen op", - "card-end": "Einde", - "card-end-on": "Eindigt op", - "editCardReceivedDatePopup-title": "Pas ontvangstdatum aan", - "editCardEndDatePopup-title": "Wijzig einddatum", - "setCardColorPopup-title": "Stel kleur in", - "setCardActionsColorPopup-title": "Kies een kleur", - "setSwimlaneColorPopup-title": "Kies een kleur", - "setListColorPopup-title": "Kies een kleur", - "assigned-by": "Toegewezen door", - "requested-by": "Aangevraagd door", - "board-delete-notice": "Verwijdering kan niet ongedaan gemaakt worden. Je raakt alle met dit bord gerelateerde lijsten, kaarten en acties kwijt.", - "delete-board-confirm-popup": "Alle lijsten, kaarten, labels en activiteiten zullen worden verwijderd en je kunt de bordinhoud niet terughalen. Er is geen herstelmogelijkheid. ", - "boardDeletePopup-title": "Bord verwijderen?", - "delete-board": "Verwijder bord", - "default-subtasks-board": "Subtaken voor __board__ bord", - "default": "Standaard", - "queue": "Rij", - "subtask-settings": "Subtaak Instellingen", - "card-settings": "Kaart Instellingen", - "boardSubtaskSettingsPopup-title": "Bord Subtaak Instellingen", - "boardCardSettingsPopup-title": "Kaart Instellingen", - "deposit-subtasks-board": "Plaats subtaken op dit bord:", - "deposit-subtasks-list": "Plaats subtaken in deze lijst:", - "show-parent-in-minicard": "Toon bron in minikaart:", - "prefix-with-full-path": "Prefix met volledig pad", - "prefix-with-parent": "Prefix met bron", - "subtext-with-full-path": "Subtekst met volledig pad", - "subtext-with-parent": "Subtekst met bron", - "change-card-parent": "Wijzig bron van kaart", - "parent-card": "Bronkaart", - "source-board": "Bronbord", - "no-parent": "Toon bron niet", - "activity-added-label": "label toegevoegd '%s' aan %s", - "activity-removed-label": "label verwijderd '%s' van %s", - "activity-delete-attach": "een bijlage verwijderd van %s", - "activity-added-label-card": "label toegevoegd '%s'", - "activity-removed-label-card": "label verwijderd '%s'", - "activity-delete-attach-card": "een bijlage verwijderd", - "activity-set-customfield": "wijzig maatwerkveld '%s' naar '%s' in %s", - "activity-unset-customfield": "wijzig maatwerkveld '%s' in %s", - "r-rule": "Regel", - "r-add-trigger": "Voeg signaal toe", - "r-add-action": "Actie toevoegen", - "r-board-rules": "Bord regels", - "r-add-rule": "Regel toevoegen", - "r-view-rule": "Toon regel", - "r-delete-rule": "Verwijder regel", - "r-new-rule-name": "Nieuwe regelnaam", - "r-no-rules": "Geen regels", - "r-when-a-card": "Als een kaart", - "r-is": "is", - "r-is-moved": "is verplaatst", - "r-added-to": "toegevoegd aan", - "r-removed-from": "verwijderd van", - "r-the-board": "het bord", - "r-list": "lijst", - "set-filter": "Definieer Filter", - "r-moved-to": "verplaatst naar", - "r-moved-from": "verplaatst van", - "r-archived": "Verplaatst naar Archief", - "r-unarchived": "Teruggehaald uit Archief", - "r-a-card": "een kaart", - "r-when-a-label-is": "Als een label is", - "r-when-the-label": "Als het label", - "r-list-name": "lijstnaam", - "r-when-a-member": "Als een lid is", - "r-when-the-member": "Als het lid", - "r-name": "naam", - "r-when-a-attach": "Als een bijlage", - "r-when-a-checklist": "Als een checklist is", - "r-when-the-checklist": "Als de checklist", - "r-completed": "Afgewerkt", - "r-made-incomplete": "Onafgewerkt gemaakt", - "r-when-a-item": "Als een checklist item is", - "r-when-the-item": "Als het checklist item", - "r-checked": "Aangevinkt", - "r-unchecked": "Uitgevinkt", - "r-move-card-to": "Verplaats kaart naar", - "r-top-of": "Bovenste van", - "r-bottom-of": "Onderste van", - "r-its-list": "zijn lijst", - "r-archive": "Verplaats naar Archief", - "r-unarchive": "Terughalen uit Archief", - "r-card": "kaart", - "r-add": "Toevoegen", - "r-remove": "Verwijder", - "r-label": "label", - "r-member": "lid", - "r-remove-all": "Verwijder alle leden van de kaart", - "r-set-color": "Wijzig kleur naar", - "r-checklist": "checklist", - "r-check-all": "Vink alles aan", - "r-uncheck-all": "Vink alles uit", - "r-items-check": "items van checklist", - "r-check": "Vink aan", - "r-uncheck": "Vink uit", - "r-item": "item", - "r-of-checklist": "van checklist", - "r-send-email": "Verzend een email", - "r-to": "naar", - "r-subject": "onderwerp", - "r-rule-details": "Regel details", - "r-d-move-to-top-gen": "Verplaats kaart helemaal naar boven op de lijst", - "r-d-move-to-top-spec": "Verplaats kaart naar bovenaan op lijst", - "r-d-move-to-bottom-gen": "Verplaats kaart naar onderaan op de lijst", - "r-d-move-to-bottom-spec": "Verplaats kaart naar onderaan op lijst", - "r-d-send-email": "Verzend email", - "r-d-send-email-to": "naar", - "r-d-send-email-subject": "onderwerp", - "r-d-send-email-message": "bericht", - "r-d-archive": "Verplaats kaart naar Archief", - "r-d-unarchive": "Haal kaart terug uit Archief", - "r-d-add-label": "Label toevoegen", - "r-d-remove-label": "Verwijder label", - "r-create-card": "Maak nieuwe kaart aan", - "r-in-list": "van lijst", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Lid toevoegen", - "r-d-remove-member": "Verwijder lid", - "r-d-remove-all-member": "Verwijder alle leden", - "r-d-check-all": "Vink alle items van een lijst aan", - "r-d-uncheck-all": "Vink alle items van een lijst uit", - "r-d-check-one": "Vink item aan", - "r-d-uncheck-one": "Vink item uit", - "r-d-check-of-list": "van checklist", - "r-d-add-checklist": "Checklist toevoegen", - "r-d-remove-checklist": "Verwijder checklist", - "r-by": "door", - "r-add-checklist": "Checklist toevoegen", - "r-with-items": "met items", - "r-items-list": "item1, item2, item3", - "r-add-swimlane": "Swimlane toevoegen", - "r-swimlane-name": "Swimlane-naam", - "r-board-note": "Let op: laat een veld leeg om er niet op te selecteren", - "r-checklist-note": "Let op: checklist items moeten geschreven worden als kommagescheiden waarden.", - "r-when-a-card-is-moved": "Als een kaart is verplaatst naar een andere lijst", - "r-set": "Wijzig", - "r-update": "Bijwerken", - "r-datefield": "datumveld", - "r-df-start-at": "begin", - "r-df-due-at": "verval", - "r-df-end-at": "einde", - "r-df-received-at": "ontvangen", - "r-to-current-datetime": "naar huidige datum/tijd", - "r-remove-value-from": "Verwijder waarde van", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authenticatiemethode", - "authentication-type": "Authenticatietype", - "custom-product-name": "Eigen Productnaam", - "layout": "Lay-out", - "hide-logo": "Verberg Logo", - "add-custom-html-after-body-start": "Voeg eigen HTML toe na <body> start", - "add-custom-html-before-body-end": "Voeg eigen HTML toe voor </body> einde", - "error-undefined": "Er is iets misgegaan", - "error-ldap-login": "Er is een fout opgetreden tijdens het inloggen", - "display-authentication-method": "Toon Authenticatiemethode", - "default-authentication-method": "Standaard Authenticatiemethode", - "duplicate-board": "Dupliceer Bord", - "people-number": "Het aantal gebruikers is:", - "swimlaneDeletePopup-title": "Swimlane verwijderen?", - "swimlane-delete-pop": "Alle acties zullen verwijderd worden van de activiteiten feed en je kunt de swimlane niet terughalen. Er is geen herstelmogelijkheid.", - "restore-all": "Haal alles terug", - "delete-all": "Verwijder alles", - "loading": "Laden, even geduld.", - "previous_as": "laatste keer was", - "act-a-dueAt": "heeft vervaldatum gewijzigd naar \nOp: __timeValue__\nKaart: __card__\noude vervaldatum was __timeOldValue__", - "act-a-endAt": "heeft einddatum gewijzigd naar __timeValue__ van (__timeOldValue__)", - "act-a-startAt": "heeft begindatum gewijzigd naar __timeValue__ van (__timeOldValue__)", - "act-a-receivedAt": "heeft ontvangstdatum gewijzigd naar __timeValue__ van (__timeOldValue__)", - "a-dueAt": "vervaldatum gewijzigd naar", - "a-endAt": "einddatum gewijzigd naar", - "a-startAt": "begindatum gewijzigd naar", - "a-receivedAt": "ontvangstdatum gewijzigd naar", - "almostdue": "huidige vervaldatum %s nadert", - "pastdue": "huidige vervaldatum %s is verlopen", - "duenow": "huidige vervaldatum %s is vandaag", - "act-newDue": "__list__/__card__ heeft eerste vervaldatum herinnering [__board__]", - "act-withDue": "__list__/__card__ vervaldatum herinneringen [__board__]", - "act-almostdue": "wil je herinneren aan het naderen van de huidige vervaldatum (__timeValue__) van __card__ ", - "act-pastdue": "wil je herinneren aan het verlopen van de huidige vervaldatum (__timeValue__) van __card__ ", - "act-duenow": "wil je herinneren aan het vandaag verlopen van de huidige vervaldatum (__timeValue__) van __card__ ", - "act-atUserComment": "Je werd genoemd in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Weet je zeker dat je dit account wilt verwijderen? Er is geen herstelmogelijkheid.", - "accounts-allowUserDelete": "Sta gebruikers toe om hun eigen account te verwijderen", - "hide-minicard-label-text": "Verberg minikaart labeltekst", - "show-desktop-drag-handles": "Toon sleep gereedschap op werkblad", - "assignee": "Toegewezen aan", - "cardAssigneesPopup-title": "Toegewezen aan", - "addmore-detail": "Voeg een meer gedetailleerde beschrijving toe", - "show-on-card": "Toon op kaart", - "new": "Nieuw", - "editUserPopup-title": "Wijzig gebruiker", - "newUserPopup-title": "Nieuwe gebruiker", - "notifications": "Meldingen", - "view-all": "Bekijk alles", - "filter-by-unread": "Filter op Ongelezen", - "mark-all-as-read": "Markeer alles als gelezen", - "allow-rename": "Sta Hernoemen toe", - "allowRenamePopup-title": "Sta Hernoemen toe" -} + "accept": "Accepteren", + "act-activity-notify": "Activiteit notificatie", + "act-addAttachment": "heeft bijlage __attachment__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-deleteAttachment": "heeft bijlage __attachment__ verwijderd op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-addSubtask": "heeft subtaak __subtask__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-addLabel": "heeft label __label__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-addedLabel": "heeft label __label__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-removeLabel": "heeft label __label__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-removedLabel": "heeft label __label__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-addChecklist": "heeft checklist __checklist__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-addChecklistItem": "heeft checklist item __checklistItem__ toegevoegd aan checklist __checklist__ op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-removeChecklist": "heeft checklist __checklist__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-removeChecklistItem": "heeft checklist item __checklistItem__ verwijderd van checklist __checkList__ op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-checkedItem": "heeft __checklistItem__ aangevinkt van checklist __checklist__ op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-uncheckedItem": "heeft __checklistItem__ uitgevinkt van checklist __checklist__ op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-completeChecklist": "heeft checklist __checklist__ afgewerkt op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-uncompleteChecklist": "heeft checklist __checklist__ onafgewerkt op kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-addComment": "heeft aantekening toegevoegd aan kaart __card__: __comment__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-editComment": "heeft aantekening gewijzigd op kaart __card__: __comment__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-deleteComment": "heeft aantekening verwijderd van kaart __card__: __comment__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-createBoard": "bord __board__ aangemaakt", + "act-createSwimlane": "swimlane __swimlane__ aangemaakt op bord __board__", + "act-createCard": "heeft kaart __card__ aangemaakt in lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-createCustomField": "heeft maatwerkveld __customField__ aangemaakt op bord __board__", + "act-deleteCustomField": "heeft maatwerkveld __customField__ verwijderd van bord __board__", + "act-setCustomField": "heeft maatwerkveld gewijzigd __customField__: __customFieldValue__ op kaart __card__ in lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-createList": "lijst __list__ toegevoegd aan bord __board__", + "act-addBoardMember": "heeft lid __member__ toegevoegd aan bord __board__", + "act-archivedBoard": "Bord __board__ verplaatst naar Archief", + "act-archivedCard": "heeft kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__ verplaatst naar Archief", + "act-archivedList": "Lijst __list__ uit swimlane __swimlane__ op bord __board__ verplaatst naar Archief", + "act-archivedSwimlane": "Swimlane __swimlane__ op bord __board__ verplaatst naar Archief", + "act-importBoard": "bord __board__ geïmporteerd", + "act-importCard": "heeft kaart __card__ geïmporteerd in lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-importList": "lijst __list__ geïmporteerd in swimlane __swimlane__ op bord __board__", + "act-joinMember": "heeft lid __member__ toegevoegd aan kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-moveCard": "heeft kaart __card__ verplaatst op bord __board__ van lijst __oldList__ uit swimlane __oldSwimlane__ naar lijst __list__ in swimlane __swimlane__", + "act-moveCardToOtherBoard": "heeft kaart __card__ verplaatst van lijst __oldList__ uit swimlane __oldSwimlane__ op bord __oldBoard__ naar lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-removeBoardMember": "heeft lid __member__ verwijderd van bord __board__", + "act-restoredCard": "heeft kaart __card__ teruggehaald naar lijst __list__ in swimlane __swimlane__ op bord __board__", + "act-unjoinMember": "heeft lid __member__ verwijderd van kaart __card__ van lijst __list__ uit swimlane __swimlane__ op bord __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Acties", + "activities": "Activiteiten", + "activity": "Activiteit", + "activity-added": "%s toegevoegd aan %s", + "activity-archived": "%s verplaatst naar Archief", + "activity-attached": "%s bijgevoegd aan %s", + "activity-created": "%s aangemaakt", + "activity-customfield-created": "maatwerkveld aangemaakt %s", + "activity-excluded": "%s uitgesloten van %s", + "activity-imported": "%s geïmporteerd in %s van %s", + "activity-imported-board": "%s geïmporteerd van %s", + "activity-joined": "%s toegetreden", + "activity-moved": "%s verplaatst van %s naar %s", + "activity-on": "bij %s", + "activity-removed": "%s verwijderd van %s", + "activity-sent": "%s gestuurd naar %s", + "activity-unjoined": "uit %s gegaan", + "activity-subtask-added": "subtaak toegevoegd aan %s", + "activity-checked-item": "%s aangevinkt in checklist %s van %s", + "activity-unchecked-item": "%s uitgevinkt in checklist %s van %s", + "activity-checklist-added": "checklist toegevoegd aan %s", + "activity-checklist-removed": "checklist verwijderd van %s", + "activity-checklist-completed": "checklist %s afgewerkt van %s", + "activity-checklist-uncompleted": "checklist %s onafgewerkt van %s", + "activity-checklist-item-added": "checklist item toegevoegd aan '%s' in '%s'", + "activity-checklist-item-removed": "checklist item verwijderd van '%s' in %s", + "add": "Toevoegen", + "activity-checked-item-card": "%s aangevinkt in checklist %s", + "activity-unchecked-item-card": "%s uitgevinkt in checklist %s", + "activity-checklist-completed-card": "heeft checklist __checklist__ afgewerkt op kaart __card__ van lijst __list__ in swimlane __swimlane__ op bord __board__", + "activity-checklist-uncompleted-card": "checklist %s onafgewerkt", + "activity-editComment": "aantekening gewijzigd %s", + "activity-deleteComment": "aantekening verwijderd %s", + "activity-receivedDate": "ontvangst datum gewijzigd naar %s van %s", + "activity-startDate": "start datum gewijzigd naar %s van %s", + "activity-dueDate": "vervaldatum gewijzigd naar %s van %s", + "activity-endDate": "einddatum gewijzigd naar %s van %s ", + "add-attachment": "Bijlage Toevoegen", + "add-board": "Bord Toevoegen", + "add-template": "Template Toevoegen", + "add-card": "Kaart Toevoegen", + "add-card-to-top-of-list": "Voeg Kaart Boven Aan de Lijst Toe", + "add-card-to-bottom-of-list": "Voeg Kaart Onder Aan de Lijst Toe", + "add-swimlane": "Swimlane Toevoegen", + "add-subtask": "Subtaak Toevoegen", + "add-checklist": "Checklist toevoegen", + "add-checklist-item": "Voeg item toe aan checklist", + "add-cover": "Cover Toevoegen", + "add-label": "Label Toevoegen", + "add-list": "Lijst Toevoegen", + "add-members": "Leden Toevoegen", + "added": "Toegevoegd", + "addMemberPopup-title": "Leden", + "admin": "Administrator", + "admin-desc": "Kan kaarten bekijken en wijzigen, leden verwijderen, en instellingen voor het bord aanpassen.", + "admin-announcement": "Melding", + "admin-announcement-active": "Systeem melding", + "admin-announcement-title": "Melding van de administrator", + "all-boards": "Alle borden", + "and-n-other-card": "En __count__ andere kaarten", + "and-n-other-card_plural": "En __count__ andere kaarten", + "apply": "Aanmelden", + "app-is-offline": "Wekan is aan het laden, wacht alstublieft. Het verversen van de pagina zorgt voor verlies van gegevens. Als Wekan niet laadt, check dan of de Wekan server niet is gestopt. ", + "archive": "Verplaats naar Archief", + "archive-all": "Verplaats Alles naar Archief", + "archive-board": "Verplaats Bord naar Archief", + "archive-card": "Verplaats Kaart naar Archief", + "archive-list": "Verplaats Lijst naar Archief", + "archive-swimlane": "Verplaats Swimlane naar Archief", + "archive-selection": "Verplaats selectie naar Archief", + "archiveBoardPopup-title": "Bord naar Archief verplaatsen?", + "archived-items": "Archief", + "archived-boards": "Borden in Archief", + "restore-board": "Herstel Bord", + "no-archived-boards": "Geen Borden in Archief.", + "archives": "Archief", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Template Container Toevoegen", + "assign-member": "Lid toevoegen", + "attached": "bijgevoegd", + "attachment": "Bijlage", + "attachment-delete-pop": "Een bijlage verwijderen is permanent. Er is geen herstelmogelijkheid.", + "attachmentDeletePopup-title": "Bijlage verwijderen?", + "attachments": "Bijlagen", + "auto-watch": "Automatisch borden bekijken wanneer deze aangemaakt worden", + "avatar-too-big": "De avatar is te groot (520KB max)", + "back": "Terug", + "board-change-color": "Wijzig kleur", + "board-nb-stars": "%s sterren", + "board-not-found": "Bord is niet gevonden", + "board-private-info": "Dit bord is nu <strong>privé</strong>.", + "board-public-info": "Dit bord is nu <strong>openbaar</strong>.", + "board-drag-drop-reorder-or-click-open": "Sleep en verplaats om iconen te herordenen. Klik bord-icoon om het bord te openen,", + "boardChangeColorPopup-title": "Verander achtergrond van bord", + "boardChangeTitlePopup-title": "Hernoem bord", + "boardChangeVisibilityPopup-title": "Verander zichtbaarheid", + "boardChangeWatchPopup-title": "Verander naar 'Watch'", + "boardMenuPopup-title": "Bord Instellingen", + "boardChangeViewPopup-title": "Bord overzicht", + "boards": "Borden", + "board-view": "Bord overzicht", + "board-view-cal": "Kalender", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Klap in", + "board-view-gantt": "Gantt", + "board-view-lists": "Lijsten", + "bucket-example": "Zoals bijvoorbeeld een \"Bucket List\"", + "cancel": "Annuleren", + "card-archived": "Deze kaart is verplaatst naar Archief.", + "board-archived": "Dit bord is verplaatst naar Archief.", + "card-comments-title": "Deze kaart heeft %s aantekening(en).", + "card-delete-notice": "Verwijdering is permanent. Je raakt alles acties die gekoppeld zijn aan deze kaart kwijt.", + "card-delete-pop": "Alle acties worden verwijderd van de activiteiten feed, en er zal geen mogelijkheid zijn om de kaart opnieuw te openen. Er is geen herstelmogelijkheid.", + "card-delete-suggest-archive": "Je kunt een kaart naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", + "card-due": "Verval", + "card-due-on": "Vervalt op ", + "card-spent": "Gespendeerde tijd", + "card-edit-attachments": "Wijzig bijlagen", + "card-edit-custom-fields": "Wijzig maatwerkvelden", + "card-edit-labels": "Wijzig labels", + "card-edit-members": "Wijzig leden", + "card-labels-title": "Wijzig de labels van de kaart.", + "card-members-title": "Voeg of verwijder leden van het bord toe aan de kaart.", + "card-start": "Begin", + "card-start-on": "Begint op", + "cardAttachmentsPopup-title": "Voeg bestand toe vanuit", + "cardCustomField-datePopup-title": "Wijzigingsdatum", + "cardCustomFieldsPopup-title": "Wijzig maatwerkvelden", + "cardStartVotingPopup-title": "Start een stemming", + "positiveVoteMembersPopup-title": "Voorstanders", + "negativeVoteMembersPopup-title": "Tegenstanders", + "card-edit-voting": "Wijzig stemming", + "editVoteEndDatePopup-title": "Wijzig einddatum stemming", + "allowNonBoardMembers": "Sta alle ingelogde gebruikers toe", + "vote-question": "Vraag", + "vote-public": "Toon wie wat gestemd heeft", + "vote-for-it": "Voor", + "vote-against": "tegen", + "deleteVotePopup-title": "Stemming verwijderen?", + "vote-delete-pop": "Verwijdering is permanent. Je raakt alle acties die gekoppeld zijn aan deze stemming kwijt.", + "cardStartPlanningPokerPopup-title": "Start Planning Poker sessie", + "card-edit-planning-poker": "Wijzig Planning Poker", + "editPokerEndDatePopup-title": "Wijzig Planning Poker ischatting einddatum", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Uitslag", + "poker-result-votes": "Inschattingen", + "poker-result-who": "Wie", + "poker-replay": "Nieuwe ronde", + "set-estimation": "Overeengekomen Inschatting", + "deletePokerPopup-title": "Planning poker verwijderen?", + "poker-delete-pop": "Verwijdering is permanent. Je verlies alle acties die gekoppeld zijn aan deze poker sessie.", + "cardDeletePopup-title": "Kaart verwijderen?", + "cardDetailsActionsPopup-title": "Kaart actie ondernemen", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Leden", + "cardMorePopup-title": "Meer", + "cardTemplatePopup-title": "Template aanmaken", + "cards": "Kaarten", + "cards-count": "Kaarten", + "cards-count-one": "Kaart", + "casSignIn": "Log in met CAS", + "cardType-card": "Kaart", + "cardType-linkedCard": "Gekoppelde Kaart", + "cardType-linkedBoard": "Gekoppeld Bord", + "change": "Wijzig", + "change-avatar": "Wijzig avatar", + "change-password": "Wijzig wachtwoord", + "change-permissions": "Wijzig permissies", + "change-settings": "Wijzig instellingen", + "changeAvatarPopup-title": "Wijzig avatar", + "changeLanguagePopup-title": "Wijzig taal", + "changePasswordPopup-title": "Wijzig wachtwoord", + "changePermissionsPopup-title": "Wijzig permissies", + "changeSettingsPopup-title": "Wijzig instellingen", + "subtasks": "Subtaken", + "checklists": "Checklists", + "click-to-star": "Klik om het bord als favoriet in te stellen", + "click-to-unstar": "Klik om het bord uit favorieten weg te halen", + "clipboard": "Vanuit clipboard of sleep het bestand hierheen", + "close": "Sluiten", + "close-board": "Sluit bord", + "close-board-pop": "Je kunt het bord terughalen door de \"Archief\" knop te klikken in de menubalk \"Mijn Borden\".", + "close-card": "Sluit Kaart", + "color-black": "zwart", + "color-blue": "blauw", + "color-crimson": "karmijn", + "color-darkgreen": "donkergroen", + "color-gold": "goud", + "color-gray": "grijs", + "color-green": "groen", + "color-indigo": "indigo", + "color-lime": "felgroen", + "color-magenta": "magenta", + "color-mistyrose": "zachtroze", + "color-navy": "marineblauw", + "color-orange": "oranje", + "color-paleturquoise": "vaalturkoois", + "color-peachpuff": "perzikroze", + "color-pink": "roze", + "color-plum": "pruim", + "color-purple": "paars", + "color-red": "rood", + "color-saddlebrown": "zadelbruin", + "color-silver": "zilver", + "color-sky": "lucht", + "color-slateblue": "leiblauw", + "color-white": "wit", + "color-yellow": "geel", + "unset-color": "Ongedefinieerd", + "comment": "Aantekening", + "comment-placeholder": "Schrijf aantekening", + "comment-only": "Alleen aantekeningen maken", + "comment-only-desc": "Kan alleen op kaarten aantekenen.", + "no-comments": "Geen aantekeningen", + "no-comments-desc": "Zie geen aantekeningen of activiteiten.", + "worker": "Medewerker", + "worker-desc": "Kan alleen kaarten verplaatsen, zichzelf aan kaarten koppelen en aantekeningen maken.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Weet je zeker dat je de subtaak wilt verwijderen?", + "confirm-checklist-delete-dialog": "Weet je zeker dat je de checklist wilt verwijderen?", + "copy-card-link-to-clipboard": "Kopieer kaart link naar klembord", + "linkCardPopup-title": "Koppel Kaart", + "searchElementPopup-title": "Zoek", + "copyCardPopup-title": "Kopieer kaart", + "copyChecklistToManyCardsPopup-title": "Checklist sjabloon kopiëren naar meerdere kaarten", + "copyChecklistToManyCardsPopup-instructions": "Doel kaart titels en omschrijvingen in dit JSON formaat", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Titel eerste kaart\", \"description\":\"Omschrijving eerste kaart\"}, {\"title\":\"Titel tweede kaart\",\"description\":\"Omschrijving tweede kaart\"},{\"title\":\"Titel laatste kaart\",\"description\":\"Omschrijving laatste kaart\"} ]", + "create": "Aanmaken", + "createBoardPopup-title": "Bord aanmaken", + "chooseBoardSourcePopup-title": "Importeer bord", + "createLabelPopup-title": "Label aanmaken", + "createCustomField": "Veld aanmaken", + "createCustomFieldPopup-title": "Veld aanmaken", + "current": "Huidige", + "custom-field-delete-pop": "Er is geen herstelmogelijkheid. Deze actie zal dit maatwerkveld van alle kaarten verwijderen en de bijbehorende historie wissen.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Valuta", + "custom-field-currency-option": "Valuta Teken", + "custom-field-date": "Datum", + "custom-field-dropdown": "Dropdown Lijst", + "custom-field-dropdown-none": "(geen)", + "custom-field-dropdown-options": "Lijst Opties", + "custom-field-dropdown-options-placeholder": "Toets Enter om meer opties toe te voegen ", + "custom-field-dropdown-unknown": "(onbekend)", + "custom-field-number": "Aantal", + "custom-field-text": "Tekst", + "custom-fields": "Maatwerkvelden", + "date": "Datum", + "decline": "Weigeren", + "default-avatar": "Standaard avatar", + "delete": "Verwijderen", + "deleteCustomFieldPopup-title": "Maatwerkveld verwijderen?", + "deleteLabelPopup-title": "Label verwijderen?", + "description": "Beschrijving", + "disambiguateMultiLabelPopup-title": "Disambigueer Label Actie", + "disambiguateMultiMemberPopup-title": "Disambigueer Lid Actie", + "discard": "Negeer", + "done": "Klaar", + "download": "Download", + "edit": "Wijzig", + "edit-avatar": "Wijzig avatar", + "edit-profile": "Wijzig profiel", + "edit-wip-limit": "Wijzig WIP limiet", + "soft-wip-limit": "Zachte WIP limiet", + "editCardStartDatePopup-title": "Wijzig start datum", + "editCardDueDatePopup-title": "Wijzig vervaldatum", + "editCustomFieldPopup-title": "Wijzig Veld", + "editCardSpentTimePopup-title": "Verander gespendeerde tijd", + "editLabelPopup-title": "Wijzig label", + "editNotificationPopup-title": "Wijzig notificatie", + "editProfilePopup-title": "Wijzig profiel", + "email": "E-mail", + "email-enrollAccount-subject": "Er is een account voor je aangemaakt op __siteName__", + "email-enrollAccount-text": "Hallo __user__,\n\nOm gebruik te maken van de online dienst, kan je op de volgende link klikken.\n\n__url__\n\nBedankt.", + "email-fail": "E-mail verzenden is mislukt", + "email-fail-text": "Fout tijdens het verzenden van de email", + "email-invalid": "Ongeldig e-mailadres", + "email-invite": "Nodig uit via e-mail", + "email-invite-subject": "__inviter__ heeft je een uitnodiging gestuurd", + "email-invite-text": "Beste __user__,\n\n__inviter__ heeft je uitgenodigd om voor een samenwerking deel te nemen aan het bord \"__board__\".\n\nKlik op de link hieronder:\n\n__url__\n\nBedankt.", + "email-resetPassword-subject": "Reset je wachtwoord op __siteName__", + "email-resetPassword-text": "Hallo __user__,\n\nKlik op de link hier beneden om je wachtwoord te resetten.\n\n__url__\n\nBedankt.", + "email-sent": "E-mail is verzonden", + "email-verifyEmail-subject": "Verifieer je e-mailadres op __siteName__", + "email-verifyEmail-text": "Hallo __user__,\n\nOm je e-mail te verifiëren vragen we je om op de link hieronder te klikken.\n\n__url__\n\nBedankt.", + "enable-wip-limit": "Activeer WIP limiet", + "error-board-doesNotExist": "Dit bord bestaat niet.", + "error-board-notAdmin": "Je moet een administrator zijn van dit bord om dat te doen.", + "error-board-notAMember": "Je moet een lid zijn van dit bord om dat te doen.", + "error-json-malformed": "JSON format klopt niet", + "error-json-schema": "De JSON data bevat niet de juiste informatie in de juiste format", + "error-csv-schema": "Je CSV(Comma Separated Values)/TSV (Tab Separated Values) bevat niet de juiste informatie in het juiste fomaat.", + "error-list-doesNotExist": "Deze lijst bestaat niet", + "error-user-doesNotExist": "Deze gebruiker bestaat niet", + "error-user-notAllowSelf": "Je kan jezelf niet uitnodigen", + "error-user-notCreated": "Deze gebruiker is niet aangemaakt", + "error-username-taken": "Deze gebruikersnaam is al in gebruik", + "error-orgname-taken": "Deze organisatienaam is al in gebruik", + "error-teamname-taken": "Deze teamnaam is al in gebruik", + "error-email-taken": "Dit e-mailadres is al in gebruik", + "export-board": "Exporteer bord", + "export-board-json": "Exporteer bord naar JSON", + "export-board-csv": "Exporteer bord naar CSV", + "export-board-tsv": "Exporteer bord naar TSV", + "export-board-excel": "Exporteer bord naar Excel", + "user-can-not-export-excel": "Gebruiker kan niet exporteren naar Excel", + "export-board-html": "Exporteer bord naar HTML", + "export-card": "Exporteer kaart", + "export-card-pdf": "Exporteer kaart naar PDF", + "user-can-not-export-card-to-pdf": "Gebruiker kan kaart niet naar PDF exporteren", + "exportBoardPopup-title": "Exporteer bord", + "exportCardPopup-title": "Exporteer kaart", + "sort": "Sorteer", + "sort-desc": "Klik om lijst te sorteren", + "list-sort-by": "Sorteer lijst op", + "list-label-modifiedAt": "Laatste toegangstijd", + "list-label-title": "Naam van de lijst", + "list-label-sort": "Je handmatige volgorde", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter kaarten of lijsten", + "filter-dates-label": "Filter op datum", + "filter-no-due-date": "Geen vervaldatum", + "filter-overdue": "verlopen", + "filter-due-today": "Vervalt vandaag", + "filter-due-this-week": "Vervalt deze week", + "filter-due-tomorrow": "Vervalt morgen", + "list-filter-label": "Filter lijst op titel", + "filter-clear": "Wis filter", + "filter-labels-label": "Filter op label", + "filter-no-label": "Geen label", + "filter-member-label": "Filter op lid", + "filter-no-member": "Geen lid", + "filter-assignee-label": "Filter op toegewezen persoon", + "filter-no-assignee": "Niemand toegewezen", + "filter-custom-fields-label": "Filter op Maatwerkveld", + "filter-no-custom-fields": "Geen maatwerkvelden", + "filter-show-archive": "Toon gearchiveerde lijsten", + "filter-hide-empty": "Verberg lege lijsten", + "filter-on": "Filter is actief", + "filter-on-desc": "Je bent nu kaarten aan het filteren op dit bord. Klik hier om het filter te wijzigen.", + "filter-to-selection": "Filter zoals selectie", + "other-filters-label": "Andere Filters", + "advanced-filter-label": "Geavanceerd Filter", + "advanced-filter-description": "Met het Geavanceerd Filter kun je een tekst schrijven die de volgende operatoren mag bevatten: == != <= >= && || ( ) Een Spatie wordt als scheiding gebruikt tussen de verschillende operatoren. Je kunt filteren op alle Maatwerkvelden door hun namen en waarden in te tikken. Bijvoorbeeld: Veld1 == Waarde1. Let op: Als velden of waarden spaties bevatten dan moet je die tussen enkele aanhalingstekens zetten. Bijvoorbeeld: 'Veld 1' == 'Waarde 1'. Om controle karakters (' \\/) over te slaan gebruik je \\. Bijvoorbeeld: Veld1 == I\\'m. Je kunt ook meerdere condities combineren. Bijvoorbeeld: F1 == V1 || F1 == V2. Normalerwijze worden alle operatoren van links naar rechts verwerkt. Dit kun je veranderen door ronde haken te gebruiken. Bijvoorbeeld: F1 == V1 && ( F2 == V2 || F2 == V3 ). Je kunt ook met regex in tekstvelden zoeken. Bijvoorbeeld: F1 == /Tes.*/i", + "fullname": "Volledige naam", + "header-logo-title": "Ga terug naar jouw borden pagina.", + "hide-system-messages": "Verberg systeemberichten", + "headerBarCreateBoardPopup-title": "Bord aanmaken", + "home": "Voorpagina", + "import": "Importeer", + "impersonate-user": "Doe als gebruiker", + "link": "Link", + "import-board": "Importeer bord", + "import-board-c": "Importeer bord", + "import-board-title-trello": "Importeer bord vanuit Trello", + "import-board-title-wekan": "Importeer bord vanuit eerdere export", + "import-board-title-csv": "Importeer bord van CSV/TSV", + "from-trello": "Vanuit Trello", + "from-wekan": "Vanuit eerdere export", + "from-csv": "Van CSV/TSV", + "import-board-instruction-trello": "Op jouw Trello bord, ga naar 'Menu', dan naar 'Meer', 'Print en Exporteer', 'Exporteer JSON', en kopieer de tekst.", + "import-board-instruction-csv": "Plak hier je Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "Ga op je bord naar 'Menu' en klik dan 'Export board' en kopieer de tekst in het gedownloade bestand.", + "import-board-instruction-about-errors": "Als je tijdens de import van het bord foutmeldingen krijgt is de import soms toch gelukt en vind je het bord terug op de 'Alle borden' scherm.", + "import-json-placeholder": "Plak geldige JSON data hier", + "import-csv-placeholder": "Plak hier je geldige CSV/TSV data", + "import-map-members": "Breng leden in kaart", + "import-members-map": "Je geïmporteerde bord heeft een aantal leden. Koppel de leden die je wilt importeren aan je gebruikers.", + "import-members-map-note": "Let op: Niet gekoppelde leden worden toegewezen aan de huidige gebruiker.", + "import-show-user-mapping": "Breng leden overzicht tevoorschijn", + "import-user-select": "Kies een bestaande gebruiker die je als dit lid wilt koppelen", + "importMapMembersAddPopup-title": "Kies lid", + "info": "Versie", + "initials": "Initialen", + "invalid-date": "Ongeldige datum", + "invalid-time": "Ongeldige tijd", + "invalid-user": "Ongeldige gebruiker", + "joined": "doet nu mee met", + "just-invited": "Je bent zojuist uitgenodigd om mee toen doen aan dit bord", + "keyboard-shortcuts": "Toetsenbord snelkoppelingen", + "label-create": "Label aanmaken", + "label-default": "%s label (standaard)", + "label-delete-pop": "Er is geen herstelmogelijkheid. Deze actie zal het label van alle kaarten verwijderen met de bijbehorende historie.", + "labels": "Labels", + "language": "Taal", + "last-admin-desc": "Je kunt de permissies niet veranderen omdat er minimaal een administrator moet zijn.", + "leave-board": "Verlaat bord", + "leave-board-pop": "Weet je zeker dat je __boardTitle__ wilt verlaten? Je wordt verwijderd van alle kaarten binnen dit bord", + "leaveBoardPopup-title": "Bord verlaten?", + "link-card": "Link naar deze kaart", + "list-archive-cards": "Verplaats alle kaarten in deze lijst naar Archief", + "list-archive-cards-pop": "Dit zal alle kaarten uit deze lijst op dit bord verwijderen. Om de kaarten in het Archief te tonen en terug te halen, klik \"Menu\" > \"Archief\".", + "list-move-cards": "Verplaats alle kaarten in deze lijst", + "list-select-cards": "Selecteer alle kaarten in deze lijst", + "set-color-list": "Wijzig kleur in", + "listActionPopup-title": "Lijst acties", + "settingsUserPopup-title": "Gebruiker Instellingen", + "settingsTeamPopup-title": "Team Instellingen", + "settingsOrgPopup-title": "Organisatie Instellingen", + "swimlaneActionPopup-title": "Swimlane handelingen", + "swimlaneAddPopup-title": "Voeg hieronder een Swimlane toe", + "listImportCardPopup-title": "Importeer een Trello kaart", + "listImportCardsTsvPopup-title": "Importeer Excel CSV/TSV", + "listMorePopup-title": "Meer", + "link-list": "Link naar deze lijst", + "list-delete-pop": "Alle acties zullen verwijderd worden van de activiteiten feed, en je zult deze niet meer kunnen herstellen. Er is geen herstelmogelijkheid.", + "list-delete-suggest-archive": "Je kunt een lijst naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", + "lists": "Lijsten", + "swimlanes": "Swimlanes", + "log-out": "Uitloggen", + "log-in": "Inloggen", + "loginPopup-title": "Inloggen", + "memberMenuPopup-title": "Leden Instellingen", + "members": "Leden", + "menu": "Menu", + "move-selection": "Verplaats selectie", + "moveCardPopup-title": "Verplaats kaart", + "moveCardToBottom-title": "Verplaats naar beneden", + "moveCardToTop-title": "Verplaats naar boven", + "moveSelectionPopup-title": "Verplaats selectie", + "multi-selection": "Multi-selectie", + "multi-selection-label": "Stel label voor selectie in", + "multi-selection-member": "Stel lid voor selectie in", + "multi-selection-on": "Multi-selectie staat aan", + "muted": "Stil", + "muted-info": "Je zal nooit meer geïnformeerd worden bij veranderingen in dit bord.", + "my-boards": "Mijn Borden", + "name": "Naam", + "no-archived-cards": "Geen kaarten in Archief.", + "no-archived-lists": "Geen lijsten in Archief..", + "no-archived-swimlanes": "Geen swimlanes in Archief.", + "no-results": "Geen resultaten", + "normal": "Normaal", + "normal-desc": "Kan de kaarten zien en wijzigen. Kan de instellingen niet wijzigen.", + "not-accepted-yet": "Uitnodiging nog niet geaccepteerd", + "notify-participate": "Ontvang updates van elke kaart die je hebt aangemaakt of lid van bent", + "notify-watch": "Ontvang updates van elke bord, lijst of kaart die je bekijkt.", + "optional": "optioneel", + "or": "of", + "page-maybe-private": "Deze pagina is privé. Je kan het bekijken door <a href='%s'>in te loggen</a>.", + "page-not-found": "Pagina niet gevonden.", + "password": "Wachtwoord", + "paste-or-dragdrop": "Om te plakken, of slepen & neer te laten (alleen afbeeldingen)", + "participating": "Deelnemen", + "preview": "Voorbeeld", + "previewAttachedImagePopup-title": "Voorbeeld", + "previewClipboardImagePopup-title": "Voorbeeld", + "private": "Privé", + "private-desc": "Dit bord is privé. Alleen gebruikers die toegevoegd zijn aan het bord kunnen het bekijken en wijzigen.", + "profile": "Profiel", + "public": "Openbaar", + "public-desc": "Dit bord is openbaar. Het is zichtbaar voor iedereen met de link en zal tevoorschijn komen op zoekmachines zoals Google. Alleen gebruikers die toegevoegd zijn aan het bord kunnen het wijzigen.", + "quick-access-description": "Maak een bord favoriet om een snelkoppeling toe te voegen aan deze balk.", + "remove-cover": "Verwijder Cover", + "remove-from-board": "Verwijder van bord", + "remove-label": "Verwijder label", + "listDeletePopup-title": "Lijst verwijderen?", + "remove-member": "Verwijder lid", + "remove-member-from-card": "Verwijder van kaart", + "remove-member-pop": "Verwijder __name__ (__username__) van __boardTitle__? Het lid zal verwijderd worden van alle kaarten op dit bord, en zal een notificatie ontvangen.", + "removeMemberPopup-title": "Lid verwijderen?", + "rename": "Hernoem", + "rename-board": "Hernoem bord", + "restore": "Herstel", + "save": "Opslaan", + "search": "Zoek", + "rules": "Regels", + "search-cards": "Zoek in kaart/lijst, titels, beschrijvingen en maatwerkvelden op dit bord", + "search-example": "Schijf je zoektekst en toets Enter", + "select-color": "Selecteer kleur", + "select-board": "Selecteer Bord", + "set-wip-limit-value": "Zet een limiet voor het maximaal aantal taken in deze lijst", + "setWipLimitPopup-title": "Zet een WIP limiet", + "shortcut-assign-self": "Voeg jezelf toe aan huidige kaart", + "shortcut-autocomplete-emoji": "Emojis automatisch aanvullen", + "shortcut-autocomplete-members": "Leden automatisch aanvullen", + "shortcut-clear-filters": "Wis alle filters", + "shortcut-close-dialog": "Sluit dialoog", + "shortcut-filter-my-cards": "Filter mijn kaarten", + "shortcut-show-shortcuts": "Haal lijst met snelkoppelingen tevoorschijn", + "shortcut-toggle-filterbar": "Wissel Filter Zijbalk", + "shortcut-toggle-searchbar": "Wissel Zoek Zijbalk", + "shortcut-toggle-sidebar": "Wissel Bord Zijbalk", + "show-cards-minimum-count": "Laat het aantal kaarten zien wanneer de lijst meer kaarten heeft dan", + "sidebar-open": "Open Zijbalk", + "sidebar-close": "Sluit Zijbalk", + "signupPopup-title": "Maak een account aan", + "star-board-title": "Klik om het bord toe te voegen aan favorieten, waarna hij aan de bovenbalk tevoorschijn komt.", + "starred-boards": "Favoriete Borden", + "starred-boards-description": "Favoriete borden komen tevoorschijn aan de bovenbalk.", + "subscribe": "Abonneer", + "team": "Team", + "this-board": "dit bord", + "this-card": "deze kaart", + "spent-time-hours": "Gespendeerde tijd (in uren)", + "overtime-hours": "Overwerk (in uren)", + "overtime": "Overwerk", + "has-overtime-cards": "Heeft kaarten met overwerk", + "has-spenttime-cards": "Heeft tijd besteed aan kaarten", + "time": "Tijd", + "title": "Titel", + "tracking": "Volgen", + "tracking-info": "Je wordt op de hoogte gesteld als er veranderingen zijn aan de kaarten waar je lid of maker van bent.", + "type": "Type", + "unassign-member": "Lid verwijderen", + "unsaved-description": "Je hebt een niet opgeslagen beschrijving.", + "unwatch": "Niet bekijken", + "upload": "Upload", + "upload-avatar": "Upload een avatar", + "uploaded-avatar": "Avatar is geüpload", + "custom-top-left-corner-logo-image-url": "URL Voor Maatwerk Logo Afbeelding In Linker Bovenhoek ", + "custom-top-left-corner-logo-link-url": "URL Voor Maatwerk Logo Link In Linker Bovenhoek", + "custom-top-left-corner-logo-height": "Hoogte Van Maatwerk Logo In Linker Bovenhoek. Default: 27", + "custom-login-logo-image-url": "URL Voor Maatwerk Login Logo Afbeelding", + "custom-login-logo-link-url": "URL Voor Maatwerk Login Logo Link", + "text-below-custom-login-logo": "Tekst onder Maatwerk Login Logo", + "automatic-linked-url-schemes": "Maatwerk URL-schema's die automatisch klikbaar zouden moeten zijn. Een URL per regel.", + "username": "Gebruikersnaam", + "import-usernames": "Importeer Gebruikersnamen", + "view-it": "Bekijk het", + "warn-list-archived": "Let op: deze kaart zit in gearchiveerde lijst", + "watch": "Bekijk", + "watching": "Bekijken", + "watching-info": "Je zal op de hoogte worden gesteld als er een verandering plaatsvind op dit bord.", + "welcome-board": "Welkom Bord", + "welcome-swimlane": "Mijlpaal 1", + "welcome-list1": "Basis", + "welcome-list2": "Geadvanceerd", + "card-templates-swimlane": "Kaart Templates", + "list-templates-swimlane": "Lijst Templates", + "board-templates-swimlane": "Bord Templates", + "what-to-do": "Wat wil je doen?", + "wipLimitErrorPopup-title": "Ongeldige WIP limiet", + "wipLimitErrorPopup-dialog-pt1": "Het aantal taken in deze lijst is groter dan de gedefinieerde WIP limiet ", + "wipLimitErrorPopup-dialog-pt2": "Verwijder een aantal taken uit deze lijst, of zet de WIP limiet hoger", + "admin-panel": "Administrator paneel", + "settings": "Instellingen", + "people": "Gebruikers", + "registration": "Registratie", + "disable-self-registration": "Schakel zelf-registratie uit", + "invite": "Uitnodigen", + "invite-people": "Nodig mensen uit", + "to-boards": "Voor bord(en)", + "email-addresses": "E-mailadressen", + "smtp-host-description": "Het adres van de SMTP server die e-mails zal versturen.", + "smtp-port-description": "De poort van de SMTP server wordt gebruikt voor uitgaande e-mails.", + "smtp-tls-description": "Gebruik TLS ondersteuning voor SMTP server.", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Poort", + "smtp-username": "Gebruikersnaam", + "smtp-password": "Wachtwoord", + "smtp-tls": "TLS ondersteuning", + "send-from": "Van", + "send-smtp-test": "Verzend een test email naar jezelf", + "invitation-code": "Uitnodigings code", + "email-invite-register-subject": "__inviter__ heeft je een uitnodiging gestuurd", + "email-invite-register-text": "Beste __user__,\n\n__inviter__ nodigt je uit voor een Kanban-bord om samen te werken.\n\nHet bord vind je via onderstaande link:\n__url__\n\nJe uitnodigingscode is: __icode__\n\nBedankt en tot ziens.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "Je hebt met succes een email verzonden", + "error-invitation-code-not-exist": "Uitnodigings code bestaat niet", + "error-notAuthorized": "Je bent niet toegestaan om deze pagina te bekijken.", + "webhook-title": "Webhook Naam", + "webhook-token": "Token (Optioneel voor Authenticatie)", + "outgoing-webhooks": "Uitgaande Webhooks", + "bidirectional-webhooks": "Twee-Weg Webhooks", + "outgoingWebhooksPopup-title": "Uitgaande Webhooks", + "boardCardTitlePopup-title": "Kaarttitel Filter", + "disable-webhook": "Schakel deze Webhook uit", + "global-webhook": "Globale Webhooks", + "new-outgoing-webhook": "Nieuwe uitgaande webhook", + "no-name": "(Onbekend)", + "Node_version": "Node versie", + "Meteor_version": "Meteor versie", + "MongoDB_version": "MongoDB versie", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog ingeschakeld", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Vrij Geheugen", + "OS_Loadavg": "OS Gemiddelde Belasting", + "OS_Platform": "OS Platform", + "OS_Release": "OS Versie", + "OS_Totalmem": "OS Totaal Geheugen", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "dagen", + "hours": "uren", + "minutes": "minuten", + "seconds": "seconden", + "show-field-on-card": "Toon dit veld op kaart", + "automatically-field-on-card": "Voeg veld toe aan nieuwe kaarten", + "always-field-on-card": "Voeg veld toe aan alle kaarten", + "showLabel-field-on-card": "Toon veldnaam op minikaart", + "yes": "Ja", + "no": "Nee", + "accounts": "Accounts", + "accounts-allowEmailChange": "Sta E-mailadres wijzigingen toe", + "accounts-allowUserNameChange": "Sta Gebruikersnaam wijzigingen toe", + "createdAt": "Aangemaakt op", + "modifiedAt": "Gewijzigd op", + "verified": "Geverifieerd", + "active": "Actief", + "card-received": "Ontvangen", + "card-received-on": "Ontvangen op", + "card-end": "Einde", + "card-end-on": "Eindigt op", + "editCardReceivedDatePopup-title": "Pas ontvangstdatum aan", + "editCardEndDatePopup-title": "Wijzig einddatum", + "setCardColorPopup-title": "Stel kleur in", + "setCardActionsColorPopup-title": "Kies een kleur", + "setSwimlaneColorPopup-title": "Kies een kleur", + "setListColorPopup-title": "Kies een kleur", + "assigned-by": "Toegewezen door", + "requested-by": "Aangevraagd door", + "card-sorting-by-number": "Kaarten sorteren op nummer", + "board-delete-notice": "Verwijdering kan niet ongedaan gemaakt worden. Je raakt alle met dit bord gerelateerde lijsten, kaarten en acties kwijt.", + "delete-board-confirm-popup": "Alle lijsten, kaarten, labels en activiteiten zullen worden verwijderd en je kunt de bordinhoud niet terughalen. Er is geen herstelmogelijkheid. ", + "boardDeletePopup-title": "Bord verwijderen?", + "delete-board": "Verwijder bord", + "default-subtasks-board": "Subtaken voor __board__ bord", + "default": "Standaard", + "queue": "Rij", + "subtask-settings": "Subtaak Instellingen", + "card-settings": "Kaart Instellingen", + "boardSubtaskSettingsPopup-title": "Bord Subtaak Instellingen", + "boardCardSettingsPopup-title": "Kaart Instellingen", + "deposit-subtasks-board": "Plaats subtaken op dit bord:", + "deposit-subtasks-list": "Plaats subtaken in deze lijst:", + "show-parent-in-minicard": "Toon bron in minikaart:", + "prefix-with-full-path": "Prefix met volledig pad", + "prefix-with-parent": "Prefix met bron", + "subtext-with-full-path": "Subtekst met volledig pad", + "subtext-with-parent": "Subtekst met bron", + "change-card-parent": "Wijzig bron van kaart", + "parent-card": "Bronkaart", + "source-board": "Bronbord", + "no-parent": "Toon bron niet", + "activity-added-label": "label toegevoegd '%s' aan %s", + "activity-removed-label": "label verwijderd '%s' van %s", + "activity-delete-attach": "een bijlage verwijderd van %s", + "activity-added-label-card": "label toegevoegd '%s'", + "activity-removed-label-card": "label verwijderd '%s'", + "activity-delete-attach-card": "een bijlage verwijderd", + "activity-set-customfield": "wijzig maatwerkveld '%s' naar '%s' in %s", + "activity-unset-customfield": "wijzig maatwerkveld '%s' in %s", + "r-rule": "Regel", + "r-add-trigger": "Voeg signaal toe", + "r-add-action": "Actie toevoegen", + "r-board-rules": "Bord regels", + "r-add-rule": "Regel toevoegen", + "r-view-rule": "Toon regel", + "r-delete-rule": "Verwijder regel", + "r-new-rule-name": "Nieuwe regelnaam", + "r-no-rules": "Geen regels", + "r-trigger": "Signaal", + "r-action": "Actie", + "r-when-a-card": "Als een kaart", + "r-is": "is", + "r-is-moved": "is verplaatst", + "r-added-to": "Toegevoegd aan", + "r-removed-from": "verwijderd van", + "r-the-board": "het bord", + "r-list": "lijst", + "list": "Lijst", + "set-filter": "Definieer Filter", + "r-moved-to": "verplaatst naar", + "r-moved-from": "verplaatst van", + "r-archived": "Verplaatst naar Archief", + "r-unarchived": "Teruggehaald uit Archief", + "r-a-card": "een kaart", + "r-when-a-label-is": "Als een label is", + "r-when-the-label": "Als het label", + "r-list-name": "lijstnaam", + "r-when-a-member": "Als een lid is", + "r-when-the-member": "Als het lid", + "r-name": "naam", + "r-when-a-attach": "Als een bijlage", + "r-when-a-checklist": "Als een checklist is", + "r-when-the-checklist": "Als de checklist", + "r-completed": "Afgewerkt", + "r-made-incomplete": "Onafgewerkt gemaakt", + "r-when-a-item": "Als een checklist item is", + "r-when-the-item": "Als het checklist item", + "r-checked": "Aangevinkt", + "r-unchecked": "Uitgevinkt", + "r-move-card-to": "Verplaats kaart naar", + "r-top-of": "Bovenste van", + "r-bottom-of": "Onderste van", + "r-its-list": "zijn lijst", + "r-archive": "Verplaats naar Archief", + "r-unarchive": "Terughalen uit Archief", + "r-card": "kaart", + "r-add": "Toevoegen", + "r-remove": "Verwijder", + "r-label": "label", + "r-member": "lid", + "r-remove-all": "Verwijder alle leden van de kaart", + "r-set-color": "Wijzig kleur naar", + "r-checklist": "checklist", + "r-check-all": "Vink alles aan", + "r-uncheck-all": "Vink alles uit", + "r-items-check": "items van checklist", + "r-check": "Vink aan", + "r-uncheck": "Vink uit", + "r-item": "item", + "r-of-checklist": "van checklist", + "r-send-email": "Verzend een email", + "r-to": "naar", + "r-of": "van", + "r-subject": "onderwerp", + "r-rule-details": "Regel details", + "r-d-move-to-top-gen": "Verplaats kaart helemaal naar boven op de lijst", + "r-d-move-to-top-spec": "Verplaats kaart naar bovenaan op lijst", + "r-d-move-to-bottom-gen": "Verplaats kaart naar onderaan op de lijst", + "r-d-move-to-bottom-spec": "Verplaats kaart naar onderaan op lijst", + "r-d-send-email": "Verzend email", + "r-d-send-email-to": "naar", + "r-d-send-email-subject": "onderwerp", + "r-d-send-email-message": "bericht", + "r-d-archive": "Verplaats kaart naar Archief", + "r-d-unarchive": "Haal kaart terug uit Archief", + "r-d-add-label": "Label toevoegen", + "r-d-remove-label": "Verwijder label", + "r-create-card": "Maak nieuwe kaart aan", + "r-in-list": "van lijst", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Lid toevoegen", + "r-d-remove-member": "Verwijder lid", + "r-d-remove-all-member": "Verwijder alle leden", + "r-d-check-all": "Vink alle items van een lijst aan", + "r-d-uncheck-all": "Vink alle items van een lijst uit", + "r-d-check-one": "Vink item aan", + "r-d-uncheck-one": "Vink item uit", + "r-d-check-of-list": "van checklist", + "r-d-add-checklist": "Checklist toevoegen", + "r-d-remove-checklist": "Verwijder checklist", + "r-by": "door", + "r-add-checklist": "Checklist toevoegen", + "r-with-items": "met items", + "r-items-list": "item1, item2, item3", + "r-add-swimlane": "Swimlane toevoegen", + "r-swimlane-name": "Swimlane-naam", + "r-board-note": "Let op: laat een veld leeg om er niet op te selecteren", + "r-checklist-note": "Let op: checklist items moeten geschreven worden als kommagescheiden waarden.", + "r-when-a-card-is-moved": "Als een kaart is verplaatst naar een andere lijst", + "r-set": "Wijzig", + "r-update": "Bijwerken", + "r-datefield": "datumveld", + "r-df-start-at": "begin", + "r-df-due-at": "verval", + "r-df-end-at": "einde", + "r-df-received-at": "ontvangen", + "r-to-current-datetime": "naar huidige datum/tijd", + "r-remove-value-from": "Verwijder waarde van", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authenticatiemethode", + "authentication-type": "Authenticatietype", + "custom-product-name": "Eigen Productnaam", + "layout": "Lay-out", + "hide-logo": "Verberg Logo", + "add-custom-html-after-body-start": "Voeg eigen HTML toe na <body> start", + "add-custom-html-before-body-end": "Voeg eigen HTML toe voor </body> einde", + "error-undefined": "Er is iets misgegaan", + "error-ldap-login": "Er is een fout opgetreden tijdens het inloggen", + "display-authentication-method": "Toon Authenticatiemethode", + "default-authentication-method": "Standaard Authenticatiemethode", + "duplicate-board": "Dupliceer Bord", + "org-number": "Het aantal organisaties is:", + "team-number": "Het aantal teams is:", + "people-number": "Het aantal gebruikers is:", + "swimlaneDeletePopup-title": "Swimlane verwijderen?", + "swimlane-delete-pop": "Alle acties zullen verwijderd worden van de activiteiten feed en je kunt de swimlane niet terughalen. Er is geen herstelmogelijkheid.", + "restore-all": "Haal alles terug", + "delete-all": "Verwijder alles", + "loading": "Laden, even geduld.", + "previous_as": "laatste keer was", + "act-a-dueAt": "heeft vervaldatum gewijzigd naar \nOp: __timeValue__\nKaart: __card__\noude vervaldatum was __timeOldValue__", + "act-a-endAt": "heeft einddatum gewijzigd naar __timeValue__ van (__timeOldValue__)", + "act-a-startAt": "heeft begindatum gewijzigd naar __timeValue__ van (__timeOldValue__)", + "act-a-receivedAt": "heeft ontvangstdatum gewijzigd naar __timeValue__ van (__timeOldValue__)", + "a-dueAt": "vervaldatum gewijzigd naar", + "a-endAt": "einddatum gewijzigd naar", + "a-startAt": "begindatum gewijzigd naar", + "a-receivedAt": "ontvangstdatum gewijzigd naar", + "almostdue": "huidige vervaldatum %s nadert", + "pastdue": "huidige vervaldatum %s is verlopen", + "duenow": "huidige vervaldatum %s is vandaag", + "act-newDue": "__list__/__card__ heeft eerste vervaldatum herinnering [__board__]", + "act-withDue": "__list__/__card__ vervaldatum herinneringen [__board__]", + "act-almostdue": "wil je herinneren aan het naderen van de huidige vervaldatum (__timeValue__) van __card__ ", + "act-pastdue": "wil je herinneren aan het verlopen van de huidige vervaldatum (__timeValue__) van __card__ ", + "act-duenow": "wil je herinneren aan het vandaag verlopen van de huidige vervaldatum (__timeValue__) van __card__ ", + "act-atUserComment": "Je werd genoemd in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Weet je zeker dat je dit account wilt verwijderen? Er is geen herstelmogelijkheid.", + "delete-team-confirm-popup": "Weet je zeker dat je dit team wilt verwijderen? Er is geen herstelmogelijkheid.", + "delete-org-confirm-popup": "Weet je zeker dat je deze organisatie wilt verwijderen? Er is geen herstelmogelijkheid.", + "accounts-allowUserDelete": "Sta gebruikers toe om hun eigen account te verwijderen", + "hide-minicard-label-text": "Verberg minikaart labeltekst", + "show-desktop-drag-handles": "Toon sleep gereedschap op werkblad", + "assignee": "Toegewezen aan", + "cardAssigneesPopup-title": "Toegewezen aan", + "addmore-detail": "Voeg een meer gedetailleerde beschrijving toe", + "show-on-card": "Toon op kaart", + "new": "Nieuw", + "editOrgPopup-title": "Wijzig organisatie", + "newOrgPopup-title": "Nieuwe organisatie", + "editTeamPopup-title": "Wijzig Team", + "newTeamPopup-title": "Nieuw Team", + "editUserPopup-title": "Wijzig gebruiker", + "newUserPopup-title": "Nieuwe gebruiker", + "notifications": "Meldingen", + "view-all": "Bekijk alles", + "filter-by-unread": "Filter op Ongelezen", + "mark-all-as-read": "Markeer alles als gelezen", + "remove-all-read": "verwijder alle gelezen", + "allow-rename": "Sta Hernoemen toe", + "allowRenamePopup-title": "Sta Hernoemen toe", + "start-day-of-week": "Stel eerste dag van de week in op", + "monday": "Maandag", + "tuesday": "Dinsdag", + "wednesday": "Woensdag", + "thursday": "Donderdag", + "friday": "Vrijdag", + "saturday": "Zaterdag", + "sunday": "Zondag", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Eigenaar", + "last-modified-at": "Laatste wijziging op", + "last-activity": "Laatste activiteit", + "voting": "Stemmen", + "archived": "Gearchiveerd", + "delete-linked-card-before-this-card": "Je kunt deze kaart niet verwijderen voordat de gekoppelde kaart is verwijderd ", + "delete-linked-cards-before-this-list": "Je kunt deze lijst niet verwijderen voordat de gekoppelde kaarten verwijderd zijn die verwijzen naar kaarten in deze lijst", + "hide-checked-items": "Verberg aangevinkte items", + "task": "Taak", + "create-task": "Taak aanmaken", + "ok": "OK", + "organizations": "Organisaties", + "teams": "Teams", + "displayName": "Schermnaam", + "shortName": "Korte naam", + "website": "Website", + "person": "Persoon", + "my-cards": "Mijn kaarten", + "card": "Kaart", + "board": "Bord", + "context-separator": "/", + "myCardsSortChange-title": "Mijn kaarten sortering", + "myCardsSortChangePopup-title": "Mijn kaarten sortering", + "myCardsSortChange-choice-board": "Naar bord", + "myCardsSortChange-choice-dueat": "Naar vervaldatum", + "dueCards-title": "Achterstallige kaarten", + "dueCardsViewChange-title": "Achterstallige kaart view", + "dueCardsViewChangePopup-title": "Achterstallige kaart view", + "dueCardsViewChange-choice-me": "Mij", + "dueCardsViewChange-choice-all": "Alle gebruikers", + "dueCardsViewChange-choice-all-description": "Toon incomplete kaarten met een *achterstallige* datum van borden waarvoor de gebruiker toegang heeft.", + "broken-cards": "Defecte kaarten", + "board-title-not-found": "Bord '%s' niet gevonden.", + "swimlane-title-not-found": "Swimlane '%s' niet gevonden.", + "list-title-not-found": "Lijst '%s' niet gevonden", + "label-not-found": "Label '%s' is niet gevonden.", + "label-color-not-found": "Labelkleur %s niet gevonden.", + "user-username-not-found": "Gebruiker '%s' niet gevonden.", + "comment-not-found": "Kaart met tekst '%s' in aantekeningen niet gevonden.", + "globalSearch-title": "Zoek op alle borden", + "no-cards-found": "Geen Kaarten Gevonden", + "one-card-found": "Een kaart gevonden", + "n-cards-found": "%s Kaarten Gevonden", + "n-n-of-n-cards-found": "__start__-__end__ van __total__ Kaarten Gevonden", + "operator-board": "bord", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "lijst", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "Gebruiker", + "operator-user-abbrev": "@", + "operator-member": "lid", + "operator-member-abbrev": "m", + "operator-assignee": "toegewezen aan", + "operator-assignee-abbrev": "a", + "operator-creator": "aangemaakt door", + "operator-status": "status", + "operator-due": "verval", + "operator-created": "aangemaakt", + "operator-modified": "gewijzigd", + "operator-sort": "sorteer", + "operator-comment": "aantekening", + "operator-has": "heeft", + "operator-limit": "limiet", + "predicate-archived": "gearchiveerd", + "predicate-open": "open", + "predicate-ended": "beëindigd", + "predicate-all": "alles", + "predicate-overdue": "verlopen", + "predicate-week": "week", + "predicate-month": "maand", + "predicate-quarter": "kwartaal", + "predicate-year": "jaar", + "predicate-due": "verval", + "predicate-modified": "gewijzigd", + "predicate-created": "aangemaakt", + "predicate-attachment": "bijlage", + "predicate-description": "beschrijving", + "predicate-checklist": "checklist", + "predicate-start": "begin", + "predicate-end": "einde", + "predicate-assignee": "toegewezen aan", + "predicate-member": "lid", + "predicate-public": "openbaar", + "predicate-private": "privé", + "operator-unknown-error": "%s is geen expressie", + "operator-number-expected": "expressie __operator__ verwachtte een getal maar kreeg '__value__'", + "operator-sort-invalid": "sortering op '%s' is ongeldig", + "operator-status-invalid": "'%s' is geen geldige status", + "operator-has-invalid": "%s is geen geldige aanwezigheidscontrole", + "operator-limit-invalid": "%s is geen geldig limiet. Een limiet moet een positief getal zijn.", + "next-page": "Volgende Pagina", + "previous-page": "Vorige Pagina", + "heading-notes": "Notities", + "globalSearch-instructions-heading": "Zoek Instructies", + "globalSearch-instructions-description": "Zoekacties kunnen expressies bevatten om het resultaat te verfijnen. Expressies worden gespecificeerd door de expressie-naam en de waarde, gescheiden door een dubbele punt. Voorbeeld, de expressie specificatie 'list:Blocked' moet het zoekresultaat limiteren tot alleen die kaarten uit een lijst met de naam *Blocked*. Als de waarde een speciaal karakter of spaties bevat dan moet deze tussen dubbele aanhalingstekens geschreven worden (bv. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Beschikbare expressies:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` -kaarten in borden die overeenkomen met *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` -kaarten in lijst die overeenkomen met *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` -kaarten in swimlanes die overeenkomen met *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` -kaarten met commentaar die bevat *<text>*", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - kaarten die een label hebben met *<color>* of *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - afkorting voor `__operator_label__:<color>` of `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - kaarten waar *<username>* is a *lid* or *actiehouder*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - afkorting voor `gebruiker:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - kaarten waar *<username>* een *lid* is", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - kaarten waar *<username>* een *actiehouder* is", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - kaarten die door *<username>* aangemaakt zijn", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - kaarten die achterstallig zijn *<n>* dagen vanaf nu. `__operator_due__:__predicate_overdue__ lists alle kaarten nu hun geplande datum.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - kaarten die zijn aangemaakt *<n>* dagen of minder geleden", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - kaarten die zijn bijgewerkt *<n>* dagen of minder geleden", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - waar *<status>* is één van de volgende:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - gearchiveerde kaarten", + "globalSearch-instructions-status-all": "`__predicate_all__` - alle gearchiveerde en niet gearchiveerde kaarten", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - kaarten met een einddatum", + "globalSearch-instructions-status-public": "`__predicate_public__` - kaarten alleen in publieke borden", + "globalSearch-instructions-status-private": "`__predicate_private__` - kaarten alleen in privé borden", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - waar *<field>* is één van `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` of `__predicate_member__`. Het plaatsen van `-` voor *<field>* zoekt naar het ontbreken van een waarde in dat veld (b.v. `has:-due` zoekt naar kaarten zonder een einddatum).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - waar *<sort-name>* is één van `__predicate_due__`, `__predicate_created__` of `__predicate_modified__`. Voor aflopende sortering, plaats een `-` voor de sorteernaam.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - waar *<n>* is een positief getal dat het aantal kaarten per pagina aanduidt.", + "globalSearch-instructions-notes-1": "Meerdere expressies mogen worden gespecificeerd.", + "globalSearch-instructions-notes-2": "Vergelijkbare expressies worden met de logische *OR* samengevoegd. Kaarten die aan een van de expressies voldoen worden als resultaat teruggegeven.\n`__operator_list__:Available __operator_list__:Blocked` geeft de kaarten die voorkomen in elke lijst met de naam *Blocked* of *Available*.", + "globalSearch-instructions-notes-3": "Uitsluitende expressies worden met de logische *AND* samengevoerd. Kaarten die aan alle expressies voldoen worden als resultaat teruggegeven. `__operator_list__:Available __operator_label__:rood` geeft alleen de kaarten in de lijst *Available* met een *rood* label.", + "globalSearch-instructions-notes-3-2": "Dagen kunnen worden aangegeven als een positief of negatief getal of gebruikmakende van `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` of `__predicate_year__` voor de huidige periode.", + "globalSearch-instructions-notes-4": "Zoeken in tekst is hoofdlettergevoelig.", + "globalSearch-instructions-notes-5": "Standaard worden gearchiveerde kaarten niet doorzocht.", + "link-to-search": "Link naar deze zoekactie", + "excel-font": "Arial", + "number": "Aantal", + "label-colors": "Labelkleuren", + "label-names": "Labelnamen", + "archived-at": "gearchiveerd op", + "sort-cards": "Sorteer Kaarten", + "cardsSortPopup-title": "Sorteer Kaarten", + "due-date": "vervaldatum", + "server-error": "Server fout", + "server-error-troubleshooting": "Stuur aub de foutmelding van de server in.\nVoor een Snap installatie: 'sudo snap logs wekan.wekan'\nVoor een Docker installatie: 'sudo docker logs wekan-app'", + "title-alphabetically": "Titel (Alfabetisch)", + "created-at-newest-first": "Aangemaakt op (Nieuwste Eerst)", + "created-at-oldest-first": "Aangemaakt op (Oudste Eerst)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Verberg systeemberichten voor alle gebruikers", + "now-system-messages-of-all-users-are-hidden": "Systeemberichten zijn nu verborgen voor alle gebruikers", + "move-swimlane": "Verplaats Swimlane", + "moveSwimlanePopup-title": "Verplaats Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (gebruik %{waarde} als tijdelijke aanduiding)", + "custom-field-stringtemplate-separator": "Scheidingsteken (gebruik of   voor een spatie)", + "custom-field-stringtemplate-item-placeholder": "Druk op Enter om meer items toe te voegen", + "creator": "Aangemaakt door", + "filesReportTitle": "Bestanden Rapportage", + "orphanedFilesReportTitle": "Verweesde Bestanden Rapportage", + "reports": "Rapportages", + "rulesReportTitle": "Regels Rapportage", + "copy-swimlane": "Kopieer Swimlane", + "copySwimlanePopup-title": "Kopieer Swimlane", + "display-card-creator": "Toon Aanmaker Kaart", + "wait-spinner": "Wacht Spinner", + "Bounce": "Stuiterende Wacht Spinner", + "Cube": "Kubus Wacht Spinner", + "Cube-Grid": "Raster-Kubus Wacht Spinner", + "Dot": "Stip Wacht Spinner", + "Double-Bounce": "Dubbel Stuiterende Wacht Spinner", + "Rotateplane": "Roterend Vlak Wacht Spinner", + "Scaleout": "Vergrotende Wacht Spinner", + "Wave": "Golvende Wacht Spinner", + "maximize-card": "Maximaliseer Kaart", + "minimize-card": "Minimaliseer Kaart", + "delete-org-warning-message": "Kan deze organisatie niet verwijderen want er is nog minimaal 1 gebruiker lid van.", + "delete-team-warning-message": "Kan dit team niet verwijderen want er is nog minimaal 1 gebruiker lid van." +} \ No newline at end of file diff --git a/i18n/oc.i18n.json b/i18n/oc.i18n.json index b1ac77628..c1cf1658b 100644 --- a/i18n/oc.i18n.json +++ b/i18n/oc.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "as rendut incomplet la checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Apondre una pèça joncha", "add-board": "Apondre un tablèu", + "add-template": "Add Template", "add-card": "Apondre una carta", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Apondre un corredor", "add-subtask": "Apondre una jos-tasca", "add-checklist": "Apondre una checklist", @@ -113,6 +120,8 @@ "archives": "Archivar", "template": "Modèl", "templates": "Modèls", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Affectar un participant", "attached": "jónher", "attachment": "pèça joncha", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Tirar la pèça joncha ?", "attachments": "Pèças jonchas", "auto-watch": "Survelhar automaticament lo tablèu un còp creat", - "avatar-too-big": "L'imatge es tròp pesuc (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Tornar", "board-change-color": "Cambiar de color", "board-nb-stars": "%s estèla", "board-not-found": "Tablèu pas trapat", "board-private-info": "Aqueste tablèu serà <strong>privat</strong>.", "board-public-info": "Aqueste tablèu serà <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Cambiar lo fons del tablèu", "boardChangeTitlePopup-title": "Tornar nomenar lo tablèu", "boardChangeVisibilityPopup-title": "Cambiar la visibilitat", @@ -138,6 +148,7 @@ "board-view-cal": "Calendièr", "board-view-swimlanes": "Corredor", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Tièras", "bucket-example": "Coma \"Tota la tièra\" per exemple", "cancel": "Tornar", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Apondut dempuèi", "cardCustomField-datePopup-title": "Cambiar la data", "cardCustomFieldsPopup-title": "Cambiar los camps personalizats", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Suprimir la carta?", "cardDetailsActionsPopup-title": "Accions sus la carta", "cardLabelsPopup-title": "Etiquetas", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Crear un modèl", "cards": "Cartas", "cards-count": "Cartas", + "cards-count-one": "Carta", "casSignIn": "Vos connectar amb CAS", "cardType-card": "Carta", "cardType-linkedCard": "Carta ligada", @@ -191,6 +236,7 @@ "close": "Tampar", "close-board": "Tampar lo tablèu", "close-board-pop": "Podètz tornar activar lo tablèu dempuèi la pagina d'acuèlh.", + "close-card": "Close Card", "color-black": "negre", "color-blue": "blau", "color-crimson": "purple clar", @@ -244,6 +290,8 @@ "current": "actual", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Casa de croiar", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Data", "custom-field-dropdown": "Tièra de causidas", "custom-field-dropdown-none": "(pas res)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Devètz èsser un participant del tablèu per far aquò", "error-json-malformed": "Vòstre tèxte es pas valid JSON", "error-json-schema": "Vòstre JSON es pas al format correct ", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Aqueste tièra existís pas", "error-user-doesNotExist": "Aqueste utilizator existís pas", "error-user-notAllowSelf": "Vos podètz pas convidar vautres meteisses", "error-user-notCreated": "Aqueste utilizator es pas encara creat", "error-username-taken": "Lo nom es ja pres", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Lo corrièl es ja pres ", "export-board": "Exportar lo tablèu", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportar lo tablèu", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtre", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Escafar lo filtre", + "filter-labels-label": "Filter by label", "filter-no-label": "Pas cap d'etiqueta", + "filter-member-label": "Filter by member", "filter-no-member": "Pas cap de participant", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "Pas de camp personalizat", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Lo filtre es activat", "filter-on-desc": "Filtratz las cartas dins aqueste tablèu. Picar aquí per editar los filtres", "filter-to-selection": "Filtrar la seleccion", + "other-filters-label": "Other Filters", "advanced-filter-label": "Filtre avançat", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nom complet", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Crear un tablèu", "home": "Acuèlh", "import": "Importar", + "impersonate-user": "Impersonate user", "link": "Ligar", "import-board": "Importar un tablèu", "import-board-c": "Importar un tablèu", "import-board-title-trello": "Importar un tablèu dempuèi Trello", "import-board-title-wekan": "Importar un tablèu dempuèi un export passat", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Importar lo tablèu va quitar totes las donadas del tablèu e lo va remplaçar amb las donadas del tablèu importat.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Dempuèi Trello", "from-wekan": "Dempuèi un export passat", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "Dins vòstre tablèu Trello, vos cal anar dins \"Menut\", puèi \"Mai\", \"Export\", \"Export JSON\", e copiar lo tèxte balhat.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "Dins vòstre tablèu, vos cal anar dins \"Menut\", puèi \"Exportar lo tablèu\", e de copiar lo tèxte del fichièr telecargat.", "import-board-instruction-about-errors": "Se avètz de errors al moment d'importar un tablèu, es possible que l'importacion as fonccionat, lo tablèu es belèu a la pagina \"Totes los tablèus\".", "import-json-placeholder": "Pegar las donadas del fichièr JSON aicí", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Mapa dels participants", "import-members-map": "Lo tablèu qu'avètz importat as ja de participants, vos cal far la migracion amb los utilizators actual", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Seleccionar un participant", @@ -375,9 +453,13 @@ "list-select-cards": "Seleccionar totas las cartas dins aquesta tièra", "set-color-list": "Set Color", "listActionPopup-title": "Tièra de las accions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Importar una carta de Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Mai", "link-list": "Ligam d'aquesta tièra", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Bolegar cap al naut", "moveSelectionPopup-title": "Bolegar la seleccion", "multi-selection": "Multi-seleccion", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Silenciós", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Cèrca", "rules": "Règlas", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Color causida", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Telecargar", "upload-avatar": "Telecargar un avatar", "uploaded-avatar": "Avatar telecargat", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Nom d’utilizaire", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Seguit", @@ -553,7 +647,8 @@ "minutes": "minutas", "seconds": "segondas", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Òc", "no": "Non", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verificat", "active": "Avtivat", "card-received": "Recebut", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Causir una color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Suprimir lo tablèu ?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Carta", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Nombre", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/pa.i18n.json b/i18n/pa.i18n.json new file mode 100644 index 000000000..f596968c0 --- /dev/null +++ b/i18n/pa.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Accept", + "act-activity-notify": "Activity Notification", + "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "created board __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Board __board__ moved to Archive", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Actions", + "activities": "Activities", + "activity": "Activity", + "activity-added": "added %s to %s", + "activity-archived": "%s moved to Archive", + "activity-attached": "attached %s to %s", + "activity-created": "created %s", + "activity-customfield-created": "created custom field %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "joined %s", + "activity-moved": "moved %s from %s to %s", + "activity-on": "on %s", + "activity-removed": "removed %s from %s", + "activity-sent": "sent %s to %s", + "activity-unjoined": "unjoined %s", + "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Add Attachment", + "add-board": "Add Board", + "add-template": "Add Template", + "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Add Swimlane", + "add-subtask": "Add Subtask", + "add-checklist": "Add Checklist", + "add-checklist-item": "Add an item to checklist", + "add-cover": "Add Cover", + "add-label": "Add Label", + "add-list": "Add List", + "add-members": "Add Members", + "added": "Added", + "addMemberPopup-title": "Members", + "admin": "Admin", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Announcement", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "All boards", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Apply", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Restore Board", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Attachment", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Attachments", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Back", + "board-change-color": "Change color", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Rename Board", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Boards", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Cancel", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Due", + "card-due-on": "Due on", + "card-spent": "Spent Time", + "card-edit-attachments": "Edit attachments", + "card-edit-custom-fields": "Edit custom fields", + "card-edit-labels": "Edit labels", + "card-edit-members": "Edit members", + "card-labels-title": "Change the labels for the card.", + "card-members-title": "Add or remove members of the board from the card.", + "card-start": "Start", + "card-start-on": "Starts on", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Delete Card?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Members", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Cards", + "cards-count": "Cards", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Change", + "change-avatar": "Change Avatar", + "change-password": "Change Password", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Change Language", + "changePasswordPopup-title": "Change Password", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Date", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Date", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Delete", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Edit", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Full Name", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "From Trello", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Invalid date", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Language", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Members", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "or", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Rename Board", + "restore": "Restore", + "save": "Save", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Time", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/pl.i18n.json b/i18n/pl.i18n.json index 403b3139a..3c0e5dc18 100644 --- a/i18n/pl.i18n.json +++ b/i18n/pl.i18n.json @@ -1,95 +1,102 @@ { "accept": "Akceptuj", "act-activity-notify": "Powiadomienia aktywności", - "act-addAttachment": "dodał(a) załącznik __attachment__ do karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-deleteAttachment": "usunął załącznik __attachment__ na karcie __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-addSubtask": "dodał(a) podzadanie __subtask__ na karcie __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-addLabel": "dodał(a) etykietę __label__ do karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-addedLabel": "dodał(a) etykietę __label__ do karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-removeLabel": "usunął etykietę __label__ z karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-removedLabel": "usunął etykietę __label__ z karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-addChecklist": "dodał(a) listę zadań __checklist__ do karty __card__ na liście __list__ w diagramie czynności __swimlane__ na tablicy __board__", - "act-addChecklistItem": "dodał(a) element listy zadań __checklistItem__ do listy zadań __checklist__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-removeChecklist": "usunął listę zadań __checklist__ z karty __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-removeChecklistItem": "usunął element listy zadań __checklistItem__ z listy zadań __checkList__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-checkedItem": "zaznaczył(a) __checklistItem__ na liście zadań __checklist__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-uncheckedItem": "odznaczył(a) __checklistItem__ na liście __checklist__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-completeChecklist": "wykonał(a) wszystkie zadania z listy __checklist__ na karcie __card__ na liście __list__ na diagramie czynności__ na tablicy __board__", - "act-uncompleteChecklist": "wycofał(a) ukończenie wykonania listy __checklist__ na karcie __card__ na liście __list__ na diagramie czynności__ na tablicy __board__", - "act-addComment": "dodał(a) komentarz na karcie __card__: __comment__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-editComment": "edytował(a) komentarz na karcie __card__: __comment__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-deleteComment": "usunął komentarz na karcie __card__: __comment__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", + "act-addAttachment": "dodał załącznik __attachment__ do karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-deleteAttachment": "usunął załącznik __attachment__ z karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addSubtask": "dodał podzadanie __subtask__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addLabel": "dodał etykietę __label__ do karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addedLabel": "dodał etykietę __label__ do karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-removeLabel": "usunął etykietę __label__ z karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-removedLabel": "usunął etykietę __label__ z karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addChecklist": "dodał czeklistę __checklist__ do karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addChecklistItem": "dodał element __checklistItem__ do czeklisty __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-removeChecklist": "usunął czeklistę __checklist__ z karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-removeChecklistItem": "usunął element __checklistItem__ z czeklisty __checkList__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-checkedItem": "zaznaczył(a) __checklistItem__ na czekliście __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-uncheckedItem": "odznaczył(a) __checklistItem__ na czekliście __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-completeChecklist": "wykonał(a) wszystkie elementy z czeklisty __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-uncompleteChecklist": "wycofał(a) ukończenie wykonania czeklisty __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-addComment": "dodał komentarz na karcie __card__: __comment__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-editComment": "edytował(a) komentarz na karcie __card__: __comment__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-deleteComment": "usunął komentarz na karcie __card__: __comment__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", "act-createBoard": "utworzył(a) tablicę __board__", - "act-createSwimlane": "utworzył(a) diagram czynności __swimlane__ na tablicy __board__", - "act-createCard": "utworzył(a) kartę __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", + "act-createSwimlane": "utworzył(a) ścieżkę __swimlane__ na tablicy __board__", + "act-createCard": "utworzył(a) kartę __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", "act-createCustomField": "utworzył(a) niestandardowe pole __customField__ na tablicy __board__", "act-deleteCustomField": "usunął niestandardowe pole __customField__ na tablicy __board__", - "act-setCustomField": "zmienił(a) niestandardowe pole __customField__: __customFieldValue__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-createList": "dodał(a) listę __list__ do tablicy __board__", - "act-addBoardMember": "dodał(a) użytykownika __member__ do tablicy __board__", + "act-setCustomField": "zmienił(a) niestandardowe pole __customField__: __customFieldValue__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-createList": "dodał listę __list__ do tablicy __board__", + "act-addBoardMember": "dodał użytkownika __member__ do tablicy __board__", "act-archivedBoard": "Tablica __board__ została przeniesiona do Archiwum", - "act-archivedCard": "przeniósł kartę __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__ do Archiwum", - "act-archivedList": "przeniósł listę __list__ na diagramie czynności __swimlane__ na tablicy __board__ do Archiwum", - "act-archivedSwimlane": "przeniósł diagram czynności __swimlane__ na tablicy __board__ do Archiwum", + "act-archivedCard": "przeniósł kartę __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__ do Archiwum", + "act-archivedList": "przeniósł listę __list__ na ścieżce __swimlane__ na tablicy __board__ do Archiwum", + "act-archivedSwimlane": "przeniósł ścieżkę __swimlane__ na tablicy __board__ do Archiwum", "act-importBoard": "zaimportował(a) tablicę __board__", - "act-importCard": "zaimportował(a) kartę __card__ do listy __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-importList": "zaimportował(a) listę __list__ na diagram czynności __swimlane__ do tablicy __board__", - "act-joinMember": "dodał(a) użytkownika __member__ do karty __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "act-moveCard": "przeniósł kartę __card__ na tablicy __board__ z listy __oldList__ na diagramie czynności __oldSwimlane__ na listę __list__ na diagramie czynności __swimlane__", - "act-moveCardToOtherBoard": "przeniósł kartę __card__ z listy __oldList__ na diagramie czynności __oldSwimlane__ na tablicy __oldBoard__ do listy __listy__ na diagramie czynności __swimlane__ na tablicy __board__", + "act-importCard": "zaimportował(a) kartę __card__ do listy __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-importList": "zaimportował(a) listę __list__ do ścieżki __swimlane__ na tablicy __board__", + "act-joinMember": "dodał użytkownika __member__ do karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-moveCard": "przeniósł kartę __card__ na tablicy __board__ z listy __oldList__ na ścieżce __oldSwimlane__ na listę __list__ na ścieżce __swimlane__", + "act-moveCardToOtherBoard": "przeniósł kartę __card__ z listy __oldList__ na ścieżce __oldSwimlane__ na tablicy __oldBoard__ do listy __listy__ na ścieżce __swimlane__ na tablicy __board__", "act-removeBoardMember": "usunął użytkownika __member__ z tablicy __board__", - "act-restoredCard": "przywrócił(a) kartę __card__ na listę __list__ na diagram czynności__ na tablicy __board__", - "act-unjoinMember": "usunął użytkownika __member__ z karty __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", + "act-restoredCard": "przywrócił(a) kartę __card__ na listę __list__ na ścieżce __swimlane__ na tablicy __board__", + "act-unjoinMember": "usunął użytkownika __member__ z karty __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", - "actions": "Akcje", + "actions": "Reakcje", "activities": "Ostatnia aktywność", "activity": "Aktywność", - "activity-added": "dodał(a) %s z %s", + "activity-added": "dodał %s do %s", "activity-archived": "%s została przeniesiona do Archiwum", - "activity-attached": "załączono %s z %s", + "activity-attached": "dodał załącznik %s do %s", "activity-created": "utworzył(a) %s", "activity-customfield-created": "utworzył(a) niestandardowe pole %s", - "activity-excluded": "wyłączono %s z %s", + "activity-excluded": "odłączył %s od %s", "activity-imported": "zaimportowano %s to %s z %s", "activity-imported-board": "zaimportowano %s z %s", - "activity-joined": "dołączono %s", - "activity-moved": "przeniesiono % z %s to %s", + "activity-joined": "dodał się do %s", + "activity-moved": "przeniósł %s z %s do %s", "activity-on": "w %s", "activity-removed": "usunięto %s z %s", "activity-sent": "wysłano %s z %s", - "activity-unjoined": "odłączono %s", + "activity-unjoined": "odłączył się od %s", "activity-subtask-added": "dodano podzadanie do %s", - "activity-checked-item": "zaznaczono %s w liście zadań%s z %s", - "activity-unchecked-item": "odznaczono %s w liście zadań %s z %s", - "activity-checklist-added": "dodał(a) listę zadań do %s", - "activity-checklist-removed": "usunął listę zadań z %s", - "activity-checklist-completed": "ukończono listę zadań %s z %s", - "activity-checklist-uncompleted": "nieukończono listy zadań %s z %s", - "activity-checklist-item-added": "dodał(a) zadanie '%s' do %s", - "activity-checklist-item-removed": "usunął element z listy zadań '%s' w %s", + "activity-checked-item": "zaznaczył %s w czekliście %s w %s", + "activity-unchecked-item": "odznaczył %s w czekliście %s w %s", + "activity-checklist-added": "dodał czeklistę do %s", + "activity-checklist-removed": "usunął czeklistę z %s", + "activity-checklist-completed": "ukończył czeklistę %s z %s", + "activity-checklist-uncompleted": "cofnął ukończenie czeklisty %s z %s", + "activity-checklist-item-added": "dodał element do czeklisty '%s' w %s", + "activity-checklist-item-removed": "usunął element '%s' z %s", "add": "Dodaj", - "activity-checked-item-card": "zaznaczono %s w liście zadań %s", - "activity-unchecked-item-card": "odznaczono %s w liście zadań %s", - "activity-checklist-completed-card": "wykonał(a) wszystkie zadania z listy __checklist__ na karcie __card__ na liście __list__ na diagramie czynności __swimlane__ na tablicy __board__", - "activity-checklist-uncompleted-card": "wycofano ukończenie listy zadań %s", + "activity-checked-item-card": "zaznaczył %s w czekliście %s", + "activity-unchecked-item-card": "odznaczył %s w czekliście %s", + "activity-checklist-completed-card": "wykonał(a) wszystkie elementy z czeklisty __checklist__ na karcie __card__ na liście __list__ na ścieżce __swimlane__ na tablicy __board__", + "activity-checklist-uncompleted-card": "wycofano ukończenie czeklisty %s", "activity-editComment": "edytował(a) komentarz %s", "activity-deleteComment": "usunął komentarz %s", + "activity-receivedDate": "zmienił datę otrzymania na %s z %s", + "activity-startDate": "zmienił datę rozpoczęcia na %s z %s", + "activity-dueDate": "zmienił datę wykonania na %s z %s", + "activity-endDate": "zmienił datę zakończenia na %s z %s", "add-attachment": "Dodaj załącznik", "add-board": "Dodaj tablicę", + "add-template": "Add Template", "add-card": "Dodaj kartę", - "add-swimlane": "Dodaj diagram czynności", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Dodaj ścieżkę", "add-subtask": "Dodaj podzadanie", - "add-checklist": "Dodaj listę kontrolną", - "add-checklist-item": "Dodaj element do listy kontrolnej", + "add-checklist": "Dodaj czeklistę", + "add-checklist-item": "Dodaj element do czeklisty", "add-cover": "Dodaj okładkę", "add-label": "Dodaj etykietę", "add-list": "Dodaj listę", - "add-members": "Dodaj członków", + "add-members": "Dodaj użytkowników", "added": "Dodane", - "addMemberPopup-title": "Członkowie", + "addMemberPopup-title": "Użytkownicy", "admin": "Administrator", - "admin-desc": "Może widzieć i edytować karty, usuwać członków oraz zmieniać ustawienia tablicy.", + "admin-desc": "Może widzieć i edytować karty, usuwać użytkowników oraz zmieniać ustawienia tablicy.", "admin-announcement": "Ogłoszenie", "admin-announcement-active": "Włącz ogłoszenie systemowe", "admin-announcement-title": "Ogłoszenie od administratora", @@ -103,7 +110,7 @@ "archive-board": "Przenieś tablicę do Archiwum", "archive-card": "Przenieś kartę do Archiwum", "archive-list": "Przenieś listę do Archiwum", - "archive-swimlane": "Przenieś diagram czynności do Archiwum", + "archive-swimlane": "Przenieś ścieżkę do Archiwum", "archive-selection": "Przenieś zaznaczone do Archiwum", "archiveBoardPopup-title": "Przenieść tablicę do Archiwum?", "archived-items": "Archiwum", @@ -113,20 +120,23 @@ "archives": "Archiwum", "template": "Szablon", "templates": "Szablony", - "assign-member": "Dodaj członka", - "attached": "załączono", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Dodaj użytkownika", + "attached": "dodał załącznik", "attachment": "Załącznik", "attachment-delete-pop": "Usunięcie załącznika jest nieodwracalne.", "attachmentDeletePopup-title": "Usunąć załącznik?", "attachments": "Załączniki", "auto-watch": "Automatycznie obserwuj tablice gdy zostaną stworzone", - "avatar-too-big": "Awatar jest za duży (maksymalnie 70KB)", + "avatar-too-big": "Awatar jest za duży (maksymalnie 520KB)", "back": "Wstecz", - "board-change-color": "Zmień kolor", + "board-change-color": "Zmień barwę", "board-nb-stars": "%s odznaczeń", "board-not-found": "Nie znaleziono tablicy", "board-private-info": "Ta tablica będzie <strong>prywatna</strong>.", "board-public-info": "Ta tablica będzie <strong>publiczna<strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Zmień tło tablicy", "boardChangeTitlePopup-title": "Zmień nazwę tablicy", "boardChangeVisibilityPopup-title": "Zmień widoczność tablicy", @@ -136,41 +146,76 @@ "boards": "Tablice", "board-view": "Widok tablicy", "board-view-cal": "Kalendarz", - "board-view-swimlanes": "Diagramy czynności", + "board-view-swimlanes": "Ścieżki", "board-view-collapse": "Zwiń", + "board-view-gantt": "Wykres Gantta", "board-view-lists": "Listy", "bucket-example": "Tak jak na przykład \"lista kubełkowa\"", "cancel": "Anuluj", "card-archived": "Ta karta została przeniesiona do Archiwum.", "board-archived": "Ta tablica została przeniesiona do Archiwum.", "card-comments-title": "Ta karta ma %s komentarzy.", - "card-delete-notice": "Usunięcie jest trwałe. Stracisz wszystkie akcje powiązane z tą kartą.", - "card-delete-pop": "Wszystkie akcje będą usunięte z widoku aktywności, nie można będzie ponownie otworzyć karty. Usunięcie jest nieodwracalne.", + "card-delete-notice": "Usunięcie jest nieodwracalne. Stracisz wszystkie zdarzenia powiązane z tą kartą.", + "card-delete-pop": "Wszystkie zdarzenia zostaną usunięte z historii aktywności i nie można będzie ponownie otworzyć karty. Usunięcie jest nieodwracalne.", "card-delete-suggest-archive": "Możesz przenieść kartę do Archiwum, a następnie usunąć ją z tablicy i zachować ją w Aktywności.", - "card-due": "Ukończenie", - "card-due-on": "Ukończenie w", - "card-spent": "Spędzony czas", + "card-due": "Data wykonania", + "card-due-on": "Data wykonania", + "card-spent": "Czas pracy", "card-edit-attachments": "Edytuj załączniki", "card-edit-custom-fields": "Edytuj niestandardowe pola", "card-edit-labels": "Edytuj etykiety", - "card-edit-members": "Edytuj członków", + "card-edit-members": "Edytuj użytkowników", "card-labels-title": "Zmień etykiety karty", - "card-members-title": "Dodaj lub usuń członków tablicy z karty.", + "card-members-title": "Dodaj lub usuń użytkowników tablicy z karty.", "card-start": "Rozpoczęcie", "card-start-on": "Zaczyna się o", "cardAttachmentsPopup-title": "Dodaj załącznik z", "cardCustomField-datePopup-title": "Zmień datę", "cardCustomFieldsPopup-title": "Edytuj niestandardowe pola", + "cardStartVotingPopup-title": "Zacznij głosowanie", + "positiveVoteMembersPopup-title": "Zwolennicy", + "negativeVoteMembersPopup-title": "Przeciwnicy", + "card-edit-voting": "Edytuj głosowanie", + "editVoteEndDatePopup-title": "Zmień głos oraz datę", + "allowNonBoardMembers": "Pozwól wszystkim zalogowanym użytkownikom", + "vote-question": "Pytanie do głosowania", + "vote-public": "Pokaż, kto głosował na wybrane opcje", + "vote-for-it": "za", + "vote-against": "przeciwko", + "deleteVotePopup-title": "Usunąć głos?", + "vote-delete-pop": "Usunięcie jest nieodwracalne. Stracisz wszystkie zdarzenia związane z tym głosowaniem.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Usunąć kartę?", - "cardDetailsActionsPopup-title": "Czynności kart", + "cardDetailsActionsPopup-title": "Działania na karcie", "cardLabelsPopup-title": "Etykiety", - "cardMembersPopup-title": "Członkowie", + "cardMembersPopup-title": "Użytkownicy", "cardMorePopup-title": "Więcej", "cardTemplatePopup-title": "Utwórz szablon", "cards": "Karty", - "cards-count": "Karty", + "cards-count": "kart", + "cards-count-one": "karty", "casSignIn": "Zaloguj się poprzez CAS", - "cardType-card": "Karta", + "cardType-card": "karty", "cardType-linkedCard": "Podpięta karta", "cardType-linkedBoard": "Podpięta tablica", "change": "Zmień", @@ -184,38 +229,39 @@ "changePermissionsPopup-title": "Zmień uprawnienia", "changeSettingsPopup-title": "Zmień ustawienia", "subtasks": "Podzadania", - "checklists": "Listy zadań", + "checklists": "Czeklisty", "click-to-star": "Kliknij by odznaczyć tę tablicę.", "click-to-unstar": "Kliknij by usunąć odznaczenie tej tablicy.", "clipboard": "Schowka lub poprzez przeciągnij & upuść", "close": "Zamknij", "close-board": "Zamknij tablicę", "close-board-pop": "Będziesz w stanie przywrócić tablicę poprzez kliknięcie przycisku \"Archiwizuj\" w nagłówku strony domowej.", - "color-black": "czarny", - "color-blue": "niebieski", - "color-crimson": "karmazynowy", - "color-darkgreen": "ciemnozielony", - "color-gold": "złoty", - "color-gray": "szary", - "color-green": "zielony", + "close-card": "Close Card", + "color-black": "czarna", + "color-blue": "niebieska", + "color-crimson": "karmazynowa", + "color-darkgreen": "ciemnozielona", + "color-gold": "złota", + "color-gray": "szara", + "color-green": "zielona", "color-indigo": "indygo", - "color-lime": "limonkowy", - "color-magenta": "fuksjowy", - "color-mistyrose": "różowy", - "color-navy": "granatowy", - "color-orange": "pomarańczowy", + "color-lime": "limonkowa", + "color-magenta": "fuksjowa", + "color-mistyrose": "jasnoróżowa", + "color-navy": "granatowa", + "color-orange": "pomarańczowa", "color-paleturquoise": "turkusowy", - "color-peachpuff": "brzoskwiniowy", - "color-pink": "różowy", - "color-plum": "śliwkowy", - "color-purple": "fioletowy", - "color-red": "czerwony", - "color-saddlebrown": "jasnobrązowy", - "color-silver": "srebrny", - "color-sky": "błękitny", - "color-slateblue": "szaroniebieski", - "color-white": "miały", - "color-yellow": "żółty", + "color-peachpuff": "brzoskwiniowa", + "color-pink": "różowa", + "color-plum": "śliwkowa", + "color-purple": "fioletowa", + "color-red": "czerwona", + "color-saddlebrown": "jasnobrązowa", + "color-silver": "srebrna", + "color-sky": "błękitna", + "color-slateblue": "szaroniebieska", + "color-white": "biała", + "color-yellow": "żółta", "unset-color": "Nieustawiony", "comment": "Komentarz", "comment-placeholder": "Dodaj komentarz", @@ -227,12 +273,12 @@ "worker-desc": "Możesz tylko przenieść karty, przypisać je do siebie i na nich komentować.", "computer": "Komputera", "confirm-subtask-delete-dialog": "Czy jesteś pewien, że chcesz usunąć to podzadanie?", - "confirm-checklist-delete-dialog": "Czy jesteś pewien, że chcesz usunąć listę zadań?", + "confirm-checklist-delete-dialog": "Czy jesteś pewien, że chcesz usunąć czeklistę?", "copy-card-link-to-clipboard": "Skopiuj łącze karty do schowka", "linkCardPopup-title": "Podepnij kartę", "searchElementPopup-title": "Wyszukaj", "copyCardPopup-title": "Skopiuj kartę", - "copyChecklistToManyCardsPopup-title": "Kopiuj szablon listy zadań do wielu kart", + "copyChecklistToManyCardsPopup-title": "Kopiuj szablon czeklisty do wielu kart", "copyChecklistToManyCardsPopup-instructions": "Docelowe tytuły i opisy kart są w formacie JSON", "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Tytuł pierwszej karty\", \"description\":\"Opis pierwszej karty\"}, {\"title\":\"Tytuł drugiej karty\",\"description\":\"Opis drugiej karty\"},{\"title\":\"Tytuł ostatniej karty\",\"description\":\"Opis ostatniej karty\"} ]", "create": "Utwórz", @@ -241,9 +287,11 @@ "createLabelPopup-title": "Utwórz etykietę", "createCustomField": "Utwórz pole", "createCustomFieldPopup-title": "Utwórz pole", - "current": "obecny", - "custom-field-delete-pop": "Nie ma możliwości wycofania tej operacji. To usunie te niestandardowe pole ze wszystkich kart oraz usunie ich całą historię.", + "current": "obecnie otwarta", + "custom-field-delete-pop": "Nie ma możliwości cofnięcia tej operacji. Usuniesz to niestandardowe pole ze wszystkich kart oraz całą jego historię.", "custom-field-checkbox": "Pole wyboru", + "custom-field-currency": "Waluta", + "custom-field-currency-option": "Kod waluty", "custom-field-date": "Data", "custom-field-dropdown": "Lista rozwijana", "custom-field-dropdown-none": "(puste)", @@ -260,10 +308,10 @@ "deleteCustomFieldPopup-title": "Usunąć niestandardowe pole?", "deleteLabelPopup-title": "Usunąć etykietę?", "description": "Opis", - "disambiguateMultiLabelPopup-title": "Ujednolić etykiety czynności", - "disambiguateMultiMemberPopup-title": "Ujednolić etykiety członków", + "disambiguateMultiLabelPopup-title": "Doprecyzuj działanie dotyczące etykiety", + "disambiguateMultiMemberPopup-title": "Doprecyzuj działanie dotyczące użytkownika", "discard": "Odrzuć", - "done": "Zrobiono", + "done": "Zakończone", "download": "Pobierz", "edit": "Edytuj", "edit-avatar": "Zmień avatar", @@ -271,9 +319,9 @@ "edit-wip-limit": "Zmień limit kart na liście", "soft-wip-limit": "Pozwól na nadmiarowe karty na liście", "editCardStartDatePopup-title": "Zmień datę rozpoczęcia", - "editCardDueDatePopup-title": "Zmień datę ukończenia", + "editCardDueDatePopup-title": "Zmień datę wykonania", "editCustomFieldPopup-title": "Edytuj pole", - "editCardSpentTimePopup-title": "Zmień spędzony czas", + "editCardSpentTimePopup-title": "Zmień ilość przepracowanego czasu", "editLabelPopup-title": "Zmień etykietę", "editNotificationPopup-title": "Zmień tryb powiadamiania", "editProfilePopup-title": "Edytuj profil", @@ -294,20 +342,34 @@ "enable-wip-limit": "Włącz limit kart na liście", "error-board-doesNotExist": "Ta tablica nie istnieje", "error-board-notAdmin": "Musisz być administratorem tej tablicy żeby to zrobić", - "error-board-notAMember": "Musisz być członkiem tej tablicy, żeby wykonać tę czynność", + "error-board-notAMember": "Musisz być użytkownikiem tej tablicy, żeby wykonać tę czynność", "error-json-malformed": "Twoja fraza nie jest w formacie JSON", "error-json-schema": "Twoje dane JSON nie zawierają prawidłowych informacji w poprawnym formacie", + "error-csv-schema": "Twój plik CSV(plik oddzielony przecinkami)/TSV (plik oddzielony tabulatorami) nie zawiera danych w poprawnym formacie", "error-list-doesNotExist": "Ta lista nie isnieje", "error-user-doesNotExist": "Ten użytkownik nie istnieje", "error-user-notAllowSelf": "Nie możesz zaprosić samego siebie", "error-user-notCreated": "Ten użytkownik nie został stworzony", "error-username-taken": "Ta nazwa jest już zajęta", + "error-orgname-taken": "Ta nazwa organizacji jest już zajęta", + "error-teamname-taken": "Ta nazwa zespołu jest już zajęta", "error-email-taken": "Adres email jest już zarezerwowany", "export-board": "Eksportuj tablicę", + "export-board-json": "Eksportuj tablicę do JSON", + "export-board-csv": "Eksportuj tablicę do CSV", + "export-board-tsv": "Eksportuj tablicę do TSV", + "export-board-excel": "Eksportuj tablicę w formacie Excel", + "user-can-not-export-excel": "Użytkownik nie może wyeksportować tablicy w formacie Excel", + "export-board-html": "Eksportuj tablicę do HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Eksportuj tablicę", + "exportCardPopup-title": "Export card", "sort": "Sortuj", "sort-desc": "Kliknij by sortować listę", "list-sort-by": "Sortuj listę przez:", - "list-label-modifiedAt": "Ostatni czas dostępu", + "list-label-modifiedAt": "Ostatni dostęp", "list-label-title": "Nazwa listy", "list-label-sort": "Twoja kolejność ustawiona ręcznie", "list-label-short-modifiedAt": "(O)", @@ -315,41 +377,57 @@ "list-label-short-sort": "(K)", "filter": "Filtr", "filter-cards": "Filtruj karty i listy", + "filter-dates-label": "Filtruj według daty", + "filter-no-due-date": "Brak terminu", + "filter-overdue": "Termin minął", + "filter-due-today": "Termin przypada dzisiaj", + "filter-due-this-week": "Termin przypada w tym tygodniu", + "filter-due-tomorrow": "Termin przypada jutro", "list-filter-label": "Filtruj listy względem tytułu", "filter-clear": "Usuń filter", + "filter-labels-label": "Filtruj wg etykiety", "filter-no-label": "Brak etykiety", - "filter-no-member": "Brak członków", + "filter-member-label": "Filtruj wg użytkownika", + "filter-no-member": "Brak użytkowników", + "filter-assignee-label": "Filtruj wg osoby przypisanej", + "filter-no-assignee": "Nieprzypisane ", + "filter-custom-fields-label": "Filtruj wg niestandardowych pól", "filter-no-custom-fields": "Brak niestandardowych pól", "filter-show-archive": "Pokaż zarchiwizowane listy", "filter-hide-empty": "Ukryj puste listy", "filter-on": "Filtr jest włączony", "filter-on-desc": "Filtrujesz karty na tej tablicy. Kliknij tutaj by edytować filtr.", "filter-to-selection": "Odfiltruj zaznaczenie", + "other-filters-label": "Inne filtry", "advanced-filter-label": "Zaawansowane filtry", - "advanced-filter-description": "Zaawansowane filtry pozwalają na wykorzystanie ciągu znaków wraz z następującymi operatorami: == != <= >= && || (). Spacja jest używana jako separator pomiędzy operatorami. Możesz przefiltrowywać wszystkie niestandardowe pola wpisując ich nazwy lub wartości, na przykład: Pole1 == Wartość1.\nUwaga: Jeśli pola lub wartości zawierają spację, musisz je zawrzeć w pojedyncze cudzysłowie, na przykład: 'Pole 1' == 'Wartość 1'. Dla pojedynczych znaków, które powinny być pominięte należy użyć \\, na przykład Pole1 == I\\'m. Możesz także wykorzystywać mieszane warunki, na przykład P1 == W1 || P1 == W2. Standardowo wszystkie operatory są interpretowane od lewej do prawej. Możesz także zmienić kolejność interpretacji wykorzystując nawiasy, na przykład P1 == W1 && (P2 == W2 || P2 == W3). Możesz także wyszukiwać tekstowo wykorzystując wyrażenia regularne, na przykład: P1 == /Tes.*/i", + "advanced-filter-description": "Zaawansowane filtry pozwalają na wykorzystanie ciągu znaków wraz z następującymi operatorami: == != <= >= && || (). Spacja jest używana jako separator pomiędzy operatorami. Możesz przefiltrowywać wszystkie niestandardowe pola wpisując ich nazwy lub wartości, na przykład: Pole1 == Wartość1.\nUwaga: Jeśli pola lub wartości zawierają spację, musisz je zawrzeć w pojedynczym cudzysłowie, na przykład: 'Pole 1' == 'Wartość 1'. Dla pojedynczych znaków, które powinny być pominięte, należy użyć \\, na przykład Pole1 == I\\'m. Możesz także wykorzystywać mieszane warunki, na przykład P1 == W1 || P1 == W2. Standardowo wszystkie operatory są interpretowane od lewej do prawej. Możesz także zmienić kolejność interpretacji wykorzystując nawiasy, na przykład P1 == W1 && (P2 == W2 || P2 == W3). Możesz także wyszukiwać tekstowo wykorzystując wyrażenia regularne, na przykład: P1 == /Tes.*/i", "fullname": "Pełna nazwa", "header-logo-title": "Wróć do swojej strony z tablicami.", "hide-system-messages": "Ukryj wiadomości systemowe", "headerBarCreateBoardPopup-title": "Utwórz tablicę", "home": "Strona główna", "import": "Importuj", + "impersonate-user": "Wciel się w tego użytkownika", "link": "Podłącz", "import-board": "importuj tablice", "import-board-c": "Import tablicy", "import-board-title-trello": "Importuj tablicę z Trello", "import-board-title-wekan": "Importuj tablicę z poprzedniego eksportu", - "import-sandstorm-backup-warning": "Nie usuwaj danych, które importujesz ze źródłowej tablicy lub Trello zanim upewnisz się, że wszystko zostało prawidłowo przeniesione przy czym brane jest pod uwagę ponowne uruchomienie strony, ponieważ w przypadku błędu braku tablicy stracisz dane.", - "import-sandstorm-warning": "Zaimportowana tablica usunie wszystkie istniejące dane na aktualnej tablicy oraz zastąpi ją danymi z tej importowanej.", + "import-board-title-csv": "Importuj tablicę z CSV/TSV", "from-trello": "Z Trello", "from-wekan": "Z poprzedniego eksportu", + "from-csv": "Z CSV/TSV", "import-board-instruction-trello": "W twojej tablicy na Trello przejdź do 'Menu', następnie 'Więcej', 'Drukuj i eksportuj', 'Eksportuj jako JSON' i skopiuj wynik", + "import-board-instruction-csv": "Wklej swoje dane w postaci wartości oddzielanych przecinkiem (CSV) lub tabulatorem (TSV)", "import-board-instruction-wekan": "Na Twojej tablicy przejdź do 'Menu', a następnie wybierz 'Eksportuj tablicę' i skopiuj tekst w pobranym pliku.", - "import-board-instruction-about-errors": "W przypadku, gdy otrzymujesz błędy importowania tablicy, czasami importowanie pomimo wszystko działa poprawnie i tablica znajduje się w oknie Wszystkie tablice.", + "import-board-instruction-about-errors": "Czasami mimo komunikatu o błędzie importowanie tablicy przebiega poprawnie i jest ona widoczna na liście tablic (Wszystkie tablice).", "import-json-placeholder": "Wklej Twoje dane JSON tutaj", - "import-map-members": "Przypisz członków", - "import-members-map": "Twoje zaimportowane tablice mają kilku członków. Proszę wybierz członków których chcesz zaimportować dla Twoich użytkowników", - "import-show-user-mapping": "Przejrzyj wybranych członków", - "import-user-select": "Wybierz istniejącego użytkownika, który ma stać się członkiem", + "import-csv-placeholder": "Wklej swoje dane z pliku CSV/TSV tutaj", + "import-map-members": "Przypisz użytkowników", + "import-members-map": "Zaimportowana tablica ma kilku użytkowników. Wybierz spośród nich osoby, które chcesz zaimportować jako użytkowników Twojej tablicy", + "import-members-map-note": "Uwaga: Karty przypisane do niezidentyfikowanych użytkowników zostaną przypisane do bieżącego użytkownika.", + "import-show-user-mapping": "Przejrzyj przypisanych użytkowników", + "import-user-select": "Wybierz istniejącego użytkownika, którego chcesz dodać", "importMapMembersAddPopup-title": "Wybierz użytkownika", "info": "Wersja", "initials": "Inicjały", @@ -373,29 +451,35 @@ "list-archive-cards-pop": "To usunie wszystkie karty z tej listy z tej tablicy. Aby przejrzeć karty w Archiwum i przywrócić na tablicę, kliknij \"Menu\" > \"Archiwizuj\".", "list-move-cards": "Przenieś wszystkie karty z tej listy", "list-select-cards": "Zaznacz wszystkie karty z tej listy", - "set-color-list": "Ustaw kolor", - "listActionPopup-title": "Lista akcji", - "swimlaneActionPopup-title": "Opcje diagramu czynności", - "swimlaneAddPopup-title": "Dodaj diagram czynności poniżej", + "set-color-list": "Ustaw barwę", + "listActionPopup-title": "Działania na liście", + "settingsUserPopup-title": "Ustawienia konta użytkownika", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Działania na ścieżce", + "swimlaneAddPopup-title": "Dodaj ścieżkę poniżej", "listImportCardPopup-title": "Zaimportuj kartę z Trello", + "listImportCardsTsvPopup-title": "Importuj CSV/TSV z Excela", "listMorePopup-title": "Więcej", "link-list": "Podepnij do tej listy", - "list-delete-pop": "Wszystkie czynności zostaną usunięte z Aktywności i nie będziesz w stanie przywrócić listy. Nie ma możliwości cofnięcia tej operacji.", + "list-delete-pop": "Wszystkie zdarzenia zostaną usunięte z historii aktywności i nie będziesz w stanie przywrócić tej listy. Nie ma możliwości cofnięcia tej operacji.", "list-delete-suggest-archive": "Możesz przenieść listę do Archiwum, a następnie usunąć ją z tablicy i zachować ją w Aktywności.", "lists": "Listy", - "swimlanes": "Diagramy czynności", + "swimlanes": "Ścieżki", "log-out": "Wyloguj", "log-in": "Zaloguj", "loginPopup-title": "Zaloguj", - "memberMenuPopup-title": "Ustawienia członków", - "members": "Członkowie", + "memberMenuPopup-title": "Ustawienia użytkowników", + "members": "Użytkownicy", "menu": "Menu", "move-selection": "Przenieś zaznaczone", "moveCardPopup-title": "Przenieś kartę", - "moveCardToBottom-title": "Przenieś na dół", - "moveCardToTop-title": "Przenieś na górę", + "moveCardToBottom-title": "Przenieś na koniec", + "moveCardToTop-title": "Przenieś na początek", "moveSelectionPopup-title": "Przenieś zaznaczone", "multi-selection": "Wielokrotne zaznaczenie", + "multi-selection-label": "Dodaj etykietę do zaznaczenia", + "multi-selection-member": "Dodaj użytkownika do zaznaczenia", "multi-selection-on": "Wielokrotne zaznaczenie jest włączone", "muted": "Wycisz", "muted-info": "Nie dostaniesz powiadomienia o zmianach w tej tablicy.", @@ -403,12 +487,12 @@ "name": "Nazwa", "no-archived-cards": "Brak kart w Archiwum.", "no-archived-lists": "Brak list w Archiwum.", - "no-archived-swimlanes": "Brak diagramów czynności w Archiwum", + "no-archived-swimlanes": "Brak ścieżek w Archiwum", "no-results": "Brak wyników", "normal": "Użytkownik standardowy", "normal-desc": "Może widzieć i edytować karty. Nie może zmieniać ustawiań.", "not-accepted-yet": "Zaproszenie jeszcze niezaakceptowane", - "notify-participate": "Otrzymuj aktualizacje kart, w których uczestniczysz jako twórca lub członek.", + "notify-participate": "Otrzymuj informacje o aktywności w kartach, które stworzyłeś(aś) lub których jesteś użytkownikiem", "notify-watch": "Otrzymuj powiadomienia z tablic, list i kart, które obserwujesz", "optional": "opcjonalny", "or": "lub", @@ -420,39 +504,41 @@ "preview": "Podgląd", "previewAttachedImagePopup-title": "Podgląd", "previewClipboardImagePopup-title": "Podgląd", - "private": "Prywatny", + "private": "Prywatna", "private-desc": "Ta tablica jest prywatna. Tylko osoby dodane do tej tablicy mogą ją zobaczyć i edytować.", "profile": "Profil", - "public": "Publiczny", + "public": "Publiczna", "public-desc": "Ta tablica jest publiczna. Jest widoczna dla wszystkich, którzy mają do niej odnośnik i będzie wynikiem silników wyszukiwania takich jak Google. Tylko użytkownicy dodani do tablicy mogą ją edytować.", "quick-access-description": "Odznacz tablicę aby dodać skrót na tym pasku.", "remove-cover": "Usuń okładkę", "remove-from-board": "Usuń z tablicy", "remove-label": "Usuń etykietę", "listDeletePopup-title": "Usunąć listę?", - "remove-member": "Usuń członka", + "remove-member": "Usuń użytkownika", "remove-member-from-card": "Usuń z karty", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Usunąć członka?", + "remove-member-pop": "Usunąć __name__ (__username__) z __boardTitle__? Użytkownik zostanie usunięty ze wszystkich kart na tej tablicy. Zostanie o tym powiadomiony.", + "removeMemberPopup-title": "Usunąć użytkownika?", "rename": "Zmień nazwę", "rename-board": "Zmień nazwę tablicy", "restore": "Przywróć", "save": "Zapisz", "search": "Wyszukaj", "rules": "Reguły", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Czego mam szukać?", - "select-color": "Wybierz kolor", - "set-wip-limit-value": "Ustaw maksymalny limit zadań na tej liście", + "search-cards": "Szukaj w tytułach kart/list oraz opisach i niestandardowych polach na tej tablicy", + "search-example": "Wpisz tekst do wyszukania i wciśnij Enter", + "select-color": "Wybierz barwę", + "select-board": "Wybierz tablicę", + "set-wip-limit-value": "Ustaw maksymalny limit kart na tej liście", "setWipLimitPopup-title": "Ustaw limit kart na liście", "shortcut-assign-self": "Przypisz siebie do obecnej karty", "shortcut-autocomplete-emoji": "Autouzupełnianie emoji", - "shortcut-autocomplete-members": "Autouzupełnianie członków", + "shortcut-autocomplete-members": "Autouzupełnianie użytkowników", "shortcut-clear-filters": "Usuń wszystkie filtry", "shortcut-close-dialog": "Zamknij okno", "shortcut-filter-my-cards": "Filtruj moje karty", "shortcut-show-shortcuts": "Przypnij do listy skrótów", "shortcut-toggle-filterbar": "Przełącz boczny pasek filtru", + "shortcut-toggle-searchbar": "Pokaż/Ukryj panel wyszukiwania", "shortcut-toggle-sidebar": "Przełącz boczny pasek tablicy", "show-cards-minimum-count": "Pokaż licznik kart, jeśli lista zawiera więcej niż", "sidebar-open": "Otwórz pasek boczny", @@ -465,23 +551,31 @@ "team": "Zespół", "this-board": "ta tablica", "this-card": "ta karta", - "spent-time-hours": "Spędzony czas (w godzinach)", - "overtime-hours": "Nadgodziny (czas)", - "overtime": "Dodatkowo", - "has-overtime-cards": "Ma dodatkowych kart", - "has-spenttime-cards": "Ma karty z wykorzystanym czasem", + "spent-time-hours": "Czas pracy (h)", + "overtime-hours": "Nadgodziny (h)", + "overtime": "Nadgodziny", + "has-overtime-cards": "Ma karty z wykazanymi nadgodzinami", + "has-spenttime-cards": "Ma karty z wykazanym czasem pracy", "time": "Czas", "title": "Tytuł", "tracking": "Śledź", - "tracking-info": "Dostaniesz powiadomienie o zmianach kart, w których bierzesz udział jako twórca lub członek.", + "tracking-info": "Będziesz powiadamiany(a) o wszelkich zmianach w kartach, które stworzyłeś(aś) lub jesteś ich użytkownikiem.", "type": "Typ", - "unassign-member": "Nieprzypisany członek", + "unassign-member": "Odłącz użytkownika", "unsaved-description": "Masz niezapisany opis.", "unwatch": "Nie obserwuj", "upload": "Wyślij", "upload-avatar": "Wyślij avatar", "uploaded-avatar": "Wysłany avatar", + "custom-top-left-corner-logo-image-url": "URL obrazu logo w lewym górnym rogu", + "custom-top-left-corner-logo-link-url": "URL linku logo w lewym górnym rogu", + "custom-top-left-corner-logo-height": "Wysokość logo w górnym lewym rogu (domyślnie: 27)", + "custom-login-logo-image-url": "URL obrazu logo ekranu logowania", + "custom-login-logo-link-url": "URL linku logo ekranu logowania", + "text-below-custom-login-logo": "Tekst pod logo na ekranie logowania", + "automatic-linked-url-schemes": "Schematy adresów URL, które powinny być automatycznie przekształcane w aktywne. Wpisz jeden schemat URL w każdej linii", "username": "Nazwa użytkownika", + "import-usernames": "Importuj użytkowników", "view-it": "Zobacz", "warn-list-archived": "Ostrzeżenie: ta karta jest na liście będącej w Archiwum", "watch": "Obserwuj", @@ -497,7 +591,7 @@ "what-to-do": "Co chcesz zrobić?", "wipLimitErrorPopup-title": "Nieprawidłowy limit kart na liście", "wipLimitErrorPopup-dialog-pt1": "Aktualna ilość kart na tej liście jest większa niż aktualny zdefiniowany limit kart.", - "wipLimitErrorPopup-dialog-pt2": "Proszę przenieś zadania z tej listy lub zmień limit kart na tej liście na wyższy.", + "wipLimitErrorPopup-dialog-pt2": "Wycofaj jakieś karty z tej listy lub zwiększ limit kart na tej liście.", "admin-panel": "Panel administracyjny", "settings": "Ustawienia", "people": "Osoby", @@ -553,29 +647,32 @@ "minutes": "minut", "seconds": "sekund", "show-field-on-card": "Pokaż te pole na karcie", - "automatically-field-on-card": "Automatycznie stwórz pole dla wszystkich kart", + "automatically-field-on-card": "Dodaj pole do nowych kart", + "always-field-on-card": "Dodaj pole do wszystkich kart", "showLabel-field-on-card": "Pokaż pole etykiety w minikarcie", "yes": "Tak", "no": "Nie", "accounts": "Konto", "accounts-allowEmailChange": "Zezwól na zmianę adresu email", "accounts-allowUserNameChange": "Zezwól na zmianę nazwy użytkownika", - "createdAt": "Stworzono o", + "createdAt": "Utworzone o", + "modifiedAt": "Zmodyfikowano", "verified": "Zweryfikowane", "active": "Aktywny", - "card-received": "Odebrano", - "card-received-on": "Odebrano", + "card-received": "Przyjęto", + "card-received-on": "Przyjęto", "card-end": "Koniec", "card-end-on": "Kończy się", - "editCardReceivedDatePopup-title": "Zmień datę odebrania", - "editCardEndDatePopup-title": "Zmień datę ukończenia", - "setCardColorPopup-title": "Ustaw kolor", - "setCardActionsColorPopup-title": "Wybierz kolor", - "setSwimlaneColorPopup-title": "Wybierz kolor", - "setListColorPopup-title": "Wybierz kolor", + "editCardReceivedDatePopup-title": "Zmień datę przyjęcia", + "editCardEndDatePopup-title": "Zmień datę zakończenia", + "setCardColorPopup-title": "Ustaw barwę", + "setCardActionsColorPopup-title": "Wybierz barwę", + "setSwimlaneColorPopup-title": "Wybierz barwę", + "setListColorPopup-title": "Wybierz barwę", "assigned-by": "Przypisane przez", "requested-by": "Zlecone przez", - "board-delete-notice": "Usuwanie jest permanentne. Stracisz wszystkie listy, kart oraz czynności przypisane do tej tablicy.", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Usunięcie jest nieodwracalne. Stracisz wszystkie listy, karty oraz reguły przypisane do tej tablicy.", "delete-board-confirm-popup": "Wszystkie listy, etykiety oraz aktywności zostaną usunięte i nie będziesz w stanie przywrócić zawartości tablicy. Tego nie da się cofnąć.", "boardDeletePopup-title": "Usunąć tablicę?", "delete-board": "Usuń tablicę", @@ -586,7 +683,7 @@ "card-settings": "Ustawienia karty", "boardSubtaskSettingsPopup-title": "Ustawienia tablicy podzadań", "boardCardSettingsPopup-title": "Ustawienia kart", - "deposit-subtasks-board": "Przechowuj podzadania na tablicy:", + "deposit-subtasks-board": "Twórz podzadania na tablicy:", "deposit-subtasks-list": "Początkowa lista dla podzadań jest przechowywana w:", "show-parent-in-minicard": "Pokaż rodzica w minikarcie:", "prefix-with-full-path": "Prefix z pełną ścieżką", @@ -597,119 +694,123 @@ "parent-card": "Karta rodzica", "source-board": "Tablica źródłowa", "no-parent": "Nie pokazuj rodzica", - "activity-added-label": "dodał(a) etykietę '%s' z '%s'", + "activity-added-label": "dodał etykietę '%s' z '%s'", "activity-removed-label": "usunął etykietę '%s' z '%s'", "activity-delete-attach": "usunął załącznik z %s", - "activity-added-label-card": "dodał(a) etykietę '%s'", + "activity-added-label-card": "dodał etykietę '%s'", "activity-removed-label-card": "usunął etykietę '%s'", "activity-delete-attach-card": "usunął załącznik", - "activity-set-customfield": "ustawiono niestandardowe pole '%s' do '%s' na '%s'", - "activity-unset-customfield": "wyczyszczono niestandardowe pole '%s' na '%s'", + "activity-set-customfield": "ustawił niestandardowe pole '%s' na '%s' w '%s'", + "activity-unset-customfield": "wyczyścił niestandardowe pole '%s' w '%s'", "r-rule": "Reguła", - "r-add-trigger": "Dodaj przełącznik", - "r-add-action": "Dodaj czynność", + "r-add-trigger": "Dodaj aktywator", + "r-add-action": "Dodaj reakcję", "r-board-rules": "Reguły tablicy", "r-add-rule": "Dodaj regułę", "r-view-rule": "Zobacz regułę", "r-delete-rule": "Usuń regułę", "r-new-rule-name": "Nowa nazwa reguły", "r-no-rules": "Brak regułę", + "r-trigger": "Aktywator", + "r-action": "Reakcja", "r-when-a-card": "Gdy karta", "r-is": "jest", "r-is-moved": "jest przenoszona", "r-added-to": "dodana do", - "r-removed-from": "usunął z", - "r-the-board": "tablicy", - "r-list": "lista", + "r-removed-from": "usunięta z", + "r-the-board": "na tablicy", + "r-list": "listy", + "list": "Lista", "set-filter": "Ustaw filtr", - "r-moved-to": "Przeniesiono do", - "r-moved-from": "Przeniesiono z", - "r-archived": "Przeniesione z Archiwum", - "r-unarchived": "Przywrócone z Archiwum", + "r-moved-to": "przenoszona do", + "r-moved-from": "przenoszona z", + "r-archived": "zarchiwizowana", + "r-unarchived": "przywrócona z archiwum", "r-a-card": "karta", "r-when-a-label-is": "Gdy etykieta jest", - "r-when-the-label": "Gdy etykieta jest", + "r-when-the-label": "Gdy etykieta", "r-list-name": "nazwa listy", - "r-when-a-member": "Gdy członek jest", - "r-when-the-member": "Gdy członek jest", + "r-when-a-member": "Gdy osoba (użytkownik) jest", + "r-when-the-member": "Gdy osoba", "r-name": "nazwa", "r-when-a-attach": "Gdy załącznik", - "r-when-a-checklist": "Gdy lista zadań jest", - "r-when-the-checklist": "Gdy lista zadań", - "r-completed": "Ukończono", - "r-made-incomplete": "Niedokończone", - "r-when-a-item": "Gdy lista zadań jest", - "r-when-the-item": "Gdy element listy zadań", - "r-checked": "Zaznaczony", - "r-unchecked": "Odznaczony", - "r-move-card-to": "Przenieś kartę do", - "r-top-of": "Góra od", - "r-bottom-of": "Dół od", + "r-when-a-checklist": "Gdy czeklista jest", + "r-when-the-checklist": "Gdy czeklista", + "r-completed": "ukończona", + "r-made-incomplete": "ponownie nieukończona", + "r-when-a-item": "Gdy element czeklisty jest", + "r-when-the-item": "Gdy element", + "r-checked": "zaznaczony", + "r-unchecked": "ponownie niezaznaczony", + "r-move-card-to": "Przenieś kartę na", + "r-top-of": "początek", + "r-bottom-of": "koniec", "r-its-list": "tej listy", - "r-archive": "Przenieś do Archiwum", + "r-archive": "Zarchiwizuj", "r-unarchive": "Przywróć z Archiwum", - "r-card": "karta", + "r-card": "kartę", "r-add": "Dodaj", "r-remove": "Usuń", - "r-label": "etykieta", - "r-member": "członek", - "r-remove-all": "Usuń wszystkich członków tej karty", - "r-set-color": "Ustaw kolor na", - "r-checklist": "lista zadań", + "r-label": "etykietę", + "r-member": "użytkownika", + "r-remove-all": "Usuń wszystkich użytkowników tej karty", + "r-set-color": "Ustaw barwę na", + "r-checklist": "czeklistę", "r-check-all": "Zaznacz wszystkie", "r-uncheck-all": "Odznacz wszystkie", - "r-items-check": "elementy listy", + "r-items-check": "elementy czeklisty", "r-check": "Zaznacz", "r-uncheck": "Odznacz", "r-item": "element", - "r-of-checklist": "z listy zadań", + "r-of-checklist": "z czeklisty", "r-send-email": "Wyślij wiadomość email", "r-to": "do", + "r-of": "z", "r-subject": "temat", "r-rule-details": "Szczegóły reguł", - "r-d-move-to-top-gen": "Przenieś kartę na górę tej listy", - "r-d-move-to-top-spec": "Przenieś kartę na górę listy", - "r-d-move-to-bottom-gen": "Przenieś kartę na dół tej listy", - "r-d-move-to-bottom-spec": "Przenieś kartę na dół listy", + "r-d-move-to-top-gen": "Przenieś kartę na początek listy", + "r-d-move-to-top-spec": "Przenieś kartę na początek listy", + "r-d-move-to-bottom-gen": "Przenieś kartę na koniec listy", + "r-d-move-to-bottom-spec": "Przenieś kartę na koniec listy", "r-d-send-email": "Wyślij wiadomość email", "r-d-send-email-to": "do", "r-d-send-email-subject": "temat", "r-d-send-email-message": "wiadomość", - "r-d-archive": "Przenieś kartę z Archiwum", + "r-d-archive": "Zarchiwizuj kartę", "r-d-unarchive": "Przywróć kartę z Archiwum", "r-d-add-label": "Dodaj etykietę", "r-d-remove-label": "Usuń etykietę", "r-create-card": "Utwórz nową kartę", "r-in-list": "na liście", - "r-in-swimlane": "w diagramie zdarzeń", - "r-d-add-member": "Dodaj członka", - "r-d-remove-member": "Usuń członka", - "r-d-remove-all-member": "Usuń wszystkich członków", + "r-in-swimlane": "na ścieżce", + "r-d-add-member": "Dodaj użytkownika", + "r-d-remove-member": "Usuń użytkownika", + "r-d-remove-all-member": "Usuń wszystkich użytkowników", "r-d-check-all": "Zaznacz wszystkie elementy listy", "r-d-uncheck-all": "Odznacz wszystkie elementy listy", "r-d-check-one": "Zaznacz element", "r-d-uncheck-one": "Odznacz element", - "r-d-check-of-list": "z listy zadań", - "r-d-add-checklist": "Dodaj listę zadań", - "r-d-remove-checklist": "Usuń listę zadań", + "r-d-check-of-list": "z czeklisty", + "r-d-add-checklist": "Dodaj czeklistę", + "r-d-remove-checklist": "Usuń czeklistę", "r-by": "przez", - "r-add-checklist": "Dodaj listę zadań", + "r-add-checklist": "Dodaj czeklistę", "r-with-items": "z elementami", "r-items-list": "element1,element2,element3", - "r-add-swimlane": "Dodaj diagram zdarzeń", - "r-swimlane-name": "Nazwa diagramu", + "r-add-swimlane": "Dodaj ścieżkę", + "r-swimlane-name": "Nazwa ścieżki", "r-board-note": "Uwaga: pozostaw pole puste, aby każda wartość była brana pod uwagę.", - "r-checklist-note": "Uwaga: wartości elementów listy muszą być oddzielone przecinkami.", - "r-when-a-card-is-moved": "Gdy karta jest przeniesiona do innej listy", + "r-checklist-note": "Uwaga: elementy czeklisty muszą być oddzielone przecinkami.", + "r-when-a-card-is-moved": "Gdy karta jest przenoszona do innej listy", "r-set": "Ustaw", "r-update": "Aktualizuj", - "r-datefield": "pole daty", - "r-df-start-at": "start", - "r-df-due-at": "rozpoczęcie", - "r-df-end-at": "zakończenie", - "r-df-received-at": "odebrano", - "r-to-current-datetime": "o aktualnej dacie/godzinie", - "r-remove-value-from": "usunął wartość z", + "r-datefield": "datę", + "r-df-start-at": "rozpoczęcia", + "r-df-due-at": "wykonania", + "r-df-end-at": "zakończenia", + "r-df-received-at": "przyjęcia", + "r-to-current-datetime": "na aktualną datę i godzinę", + "r-remove-value-from": "Wyczyść", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", @@ -725,45 +826,237 @@ "display-authentication-method": "Wyświetl metodę logowania", "default-authentication-method": "Domyślna metoda logowania", "duplicate-board": "Duplikuj tablicę", + "org-number": "Liczba organizacji:", + "team-number": "Liczba zespołów:", "people-number": "Liczba użytkowników to:", - "swimlaneDeletePopup-title": "Usunąć diagram czynności?", - "swimlane-delete-pop": "Wszystkie akcje będą usunięte z widoku aktywności, nie można będzie przywrócić diagramu czynności. Usunięcie jest nieodwracalne.", + "swimlaneDeletePopup-title": "Usunąć ścieżkę?", + "swimlane-delete-pop": "Wszystkie zdarzenia dotyczące tej ścieżki zostaną usunięte z historii aktywności. Tej operacji nie można cofnąć. Usunięcie jest nieodwracalne.", "restore-all": "Przywróć wszystkie", "delete-all": "Usuń wszystkie", "loading": "Ładowanie, proszę czekać.", - "previous_as": "ostatni czas był", - "act-a-dueAt": "zmienił(a) czas zakończenia na: __timeValue__ w karcie __card__, poprzedni czas: __timeOldValue__", - "act-a-endAt": "zmienił(a) czas zakończenia na __timeValue__ z __timeOldValue__", - "act-a-startAt": "zmienił(a) czas rozpoczęcia na __timeValue__ z __timeOldValue__", - "act-a-receivedAt": "zmienił(a) czas odebrania zadania na __timeValue__ z __timeOldValue__", - "a-dueAt": "zmieniono czas zakończenia na", - "a-endAt": "zmieniono czas zakończenia na", - "a-startAt": "zmieniono czas startu na", - "a-receivedAt": "zmieniono czas odebrania zadania na", - "almostdue": "aktualny termin ukończenia %s dobiega końca", - "pastdue": "aktualny termin ukończenia %s jest w przeszłości", - "duenow": "aktualny termin ukończenia %s jest dzisiaj", - "act-newDue": "__list__/__card__ przypomina o 1szym zakończeniu terminu [__board__]", - "act-withDue": "__list__/__card__ posiada przypomnienia zakończenia terminu [__board__]", - "act-almostdue": "przypomina o zbliżającej się dacie ukończenia (__timeValue__) karty __card__", - "act-pastdue": "przypomina o ubiegłej dacie ukończenia (__timeValue__) karty __card__", - "act-duenow": "przypomina o ubiegającej teraz dacie ukończenia (__timeValue__) karty __card__", + "previous_as": "ostatni raz był", + "act-a-dueAt": "zmienił(a) datę wykonania na: __timeValue__ w karcie __card__, poprzednia data wykonania: __timeOldValue__", + "act-a-endAt": "zmienił czas zakończenia na __timeValue__ z __timeOldValue__", + "act-a-startAt": "zmienił czas rozpoczęcia na __timeValue__ z __timeOldValue__", + "act-a-receivedAt": "zmienił czas przyjęcia zadania na __timeValue__ z __timeOldValue__", + "a-dueAt": "zmienił datę wykonania na", + "a-endAt": "zmienił czas zakończenia na", + "a-startAt": "zmienił czas rozpoczęcia na", + "a-receivedAt": "zmienił czas przyjęcia zadania na", + "almostdue": "data wykonania %s jest bliska", + "pastdue": "data wykonania %s minęła", + "duenow": "data wykonania %s przypada dzisiaj", + "act-newDue": "__list__/__card__ pierwszy raz przypomina o dacie wykonania [__board__]", + "act-withDue": "__list__/__card__ ma przypomnienia o dacie wykonania [__board__]", + "act-almostdue": "przypominał o zbliżającej się dacie wykonania (__timeValue__) karty __card__", + "act-pastdue": "przypominał o upłynięciu daty wykonania (__timeValue__) karty __card__", + "act-duenow": "przypominał o mijającej właśnie dacie wykonania (__timeValue__) karty __card__", "act-atUserComment": "Zostałeś wspomniany w [__board] __list__/__card__", "delete-user-confirm-popup": "Czy jesteś pewien, że chcesz usunąć te konto? Nie można tego wycofać.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Pozwól użytkownikom na usuwanie własnych kont", "hide-minicard-label-text": "Ukryj opisy etykiet minikart", "show-desktop-drag-handles": "Pokaż przeciągnięcia na pulpit", - "assignee": "Przypisujący", - "cardAssigneesPopup-title": "Przypisujący", + "assignee": "Przypisani", + "cardAssigneesPopup-title": "Przypisani", "addmore-detail": "Dodaj bardziej szczegółowy opis", "show-on-card": "Pokaż na karcie", "new": "Nowy", + "editOrgPopup-title": "Edytuj organizację", + "newOrgPopup-title": "Nowa organizacja", + "editTeamPopup-title": "Edytuj zespół", + "newTeamPopup-title": "Nowy zespół", "editUserPopup-title": "Edytuj użytkownika", "newUserPopup-title": "Nowy użytkownik", "notifications": "Powiadomienia", "view-all": "Wyświetl wszystko", "filter-by-unread": "Filtruj nieprzeczytane", "mark-all-as-read": "Zaznacz wszystkie jako przeczytane", + "remove-all-read": "Usuń wszystkie przeczytane", "allow-rename": "Zezwól na zmianę nazwy", - "allowRenamePopup-title": "Zezwól na zmianę nazwy" + "allowRenamePopup-title": "Zezwól na zmianę nazwy", + "start-day-of-week": "Wybierz pierwszy dzień tygodnia", + "monday": "Poniedziałek", + "tuesday": "Wtorek", + "wednesday": "Środa", + "thursday": "Czwartek", + "friday": "Piątek", + "saturday": "Sobota", + "sunday": "Niedziela", + "status": "Status", + "swimlane": "Ścieżka", + "owner": "Właściciel", + "last-modified-at": "Ostatnio zmodyfikowano", + "last-activity": "Ostatnia aktywność", + "voting": "Głosowanie", + "archived": "Zarchiwizowany", + "delete-linked-card-before-this-card": "Nie możesz usunąć tej karty, dopóki nie usuniesz podpiętej karty, w której są", + "delete-linked-cards-before-this-list": "Nie możesz usunąć tej karty, dopóki nie usuniesz podpiętych kart, które wskazują na karty w tej liście", + "hide-checked-items": "Ukryj ukończone", + "task": "Zadanie", + "create-task": "Utwórz zadanie", + "ok": "OK", + "organizations": "Organizacje", + "teams": "Zespoły", + "displayName": "Nazwa wyświetlana", + "shortName": "Nazwa skrócona", + "website": "Strona internetowa", + "person": "Osoba", + "my-cards": "Moje karty", + "card": "karty", + "board": "Tablica", + "context-separator": "/", + "myCardsSortChange-title": "Moje karty sortuj", + "myCardsSortChangePopup-title": "Moje karty sortuj", + "myCardsSortChange-choice-board": "według tablic", + "myCardsSortChange-choice-dueat": "według terminów", + "dueCards-title": "Karty oczekujące", + "dueCardsViewChange-title": "Lista kart oczekujących", + "dueCardsViewChangePopup-title": "Lista kart oczekujących", + "dueCardsViewChange-choice-me": "Moje", + "dueCardsViewChange-choice-all": "Wszystkich użytkowników", + "dueCardsViewChange-choice-all-description": "Wyświetla wszystkie nieukończone karty z ustawioną datą wykonania ze wszystkich tablic, do których użytkownik ma dostęp.", + "broken-cards": "Karty wadliwe", + "board-title-not-found": "Nie znaleziono tablicy '%s'.", + "swimlane-title-not-found": "Nie znaleziono ścieżki '%s'.", + "list-title-not-found": "Nie znaleziono listy '%s'.", + "label-not-found": "Nie znaleziono etykiety '%s'.", + "label-color-not-found": "Kolor etykiety %s nie istnieje.", + "user-username-not-found": "Nie znaleziono użytkownika '%s'.", + "comment-not-found": "Nie znaleziono karty z komentarzem zawierającym '%s'.", + "globalSearch-title": "Przeszukaj wszystkie tablice", + "no-cards-found": "Nie znaleziono kart", + "one-card-found": "Znaleziono 1 kartę", + "n-cards-found": "Znaleziono %skart(y)", + "n-n-of-n-cards-found": "__start__-__end__ z __total__ znalezionych kart", + "operator-board": "tablica", + "operator-board-abbrev": "t", + "operator-swimlane": "ścieżka", + "operator-swimlane-abbrev": "s", + "operator-list": "listy", + "operator-list-abbrev": "l", + "operator-label": "etykietę", + "operator-label-abbrev": "#", + "operator-user": "użytkownik", + "operator-user-abbrev": "@", + "operator-member": "użytkownika", + "operator-member-abbrev": "u", + "operator-assignee": "przypisany", + "operator-assignee-abbrev": "p", + "operator-creator": "twórca", + "operator-status": "status", + "operator-due": "wykonania", + "operator-created": "stworzono", + "operator-modified": "zmodyfikowano", + "operator-sort": "kategoria", + "operator-comment": "komentarz", + "operator-has": "ma", + "operator-limit": "limit", + "predicate-archived": "zarchiwizowane", + "predicate-open": "rozpoczęte", + "predicate-ended": "ukończone", + "predicate-all": "wszystkie", + "predicate-overdue": "przeterminowane", + "predicate-week": "tydzień", + "predicate-month": "miesiąc", + "predicate-quarter": "kwartał", + "predicate-year": "rok", + "predicate-due": "wykonania", + "predicate-modified": "zmodyfikowano", + "predicate-created": "stworzono", + "predicate-attachment": "załącznik", + "predicate-description": "opis", + "predicate-checklist": "czeklistę", + "predicate-start": "rozpoczęcia", + "predicate-end": "zakończenia", + "predicate-assignee": "przypisany", + "predicate-member": "użytkownika", + "predicate-public": "publiczne", + "predicate-private": "prywatne", + "operator-unknown-error": "%s nie jest operatorem", + "operator-number-expected": "operator __operator__ oczekiwał wartości cyfrowej, otrzymał '__value__'", + "operator-sort-invalid": "kategoria '%s' jest niepoprawna", + "operator-status-invalid": "status '%s' jest niepoprawny", + "operator-has-invalid": "%s nie jest poprawnym sprawdzeniem istnienia", + "operator-limit-invalid": "%s nie jest poprawnym limitem. Limit musi być liczbą naturalną.", + "next-page": "Następna strona", + "previous-page": "Poprzednia strona", + "heading-notes": "Notatki", + "globalSearch-instructions-heading": "Instrukcja wyszukiwania", + "globalSearch-instructions-description": "W wyszukiwanym ciągu można umieszczać specjalne klucze, aby doprecyzować zapytanie. Klucz składa się z operatora i argumentu, rozdzielonych dwukropkiem. Na przykład klucz `lista:Wstrzymane` zawęża wyszukiwanie do kart znajdujących się na listach o nazwie *Wstrzymane*. Jeśli argument zawiera spacje lub znaki specjalne, należy go umieścić w cudzysłowie, np. `__operator_list__:\"Do uzgodnienia\"`.", + "globalSearch-instructions-operators": "Dostępne klucze wyszukiwania:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - karty na tablicach pasujących do klucza *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - karty na listach pasujących do klucza *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - karty na ścieżkach pasujących do klucza *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - karty z komentarzem zawierającym *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - karty z etykietą pasującą do *<color>* lub *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - skrót dla `__operator_label__:<color>` lub `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - karty, których *użytkownikiem* lub *osobą przypisaną* jest *<username>*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - skrócona forma dla `użytkownik:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - karty, których *użytkownikiem* jest *<username>*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - karty, do których *przypisany* jest użytkownik *<username>*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - karty, których twórcą jest *<username>*", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - karty, których termin mija w ciągu *<n>* lub mniej dni od teraz. `__operator_due__:__predicate_overdue__ wyświetla wszystkie karty, których termin minął.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - karty utworzone *<n>* lub mniej dni temu", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - karty zmodyfikowane *<n>* lub mniej dni temu", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - gdzie *<status>* jest jednym z:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - karty zarchiwizowane", + "globalSearch-instructions-status-all": "`__predicate_all__` - wszystkie karty zarchiwizowane i przywrócone z archiwum", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - karty z podanym terminem zakończenia", + "globalSearch-instructions-status-public": "`__predicate_public__` - karty wyłącznie na publicznych tablicach", + "globalSearch-instructions-status-private": "`__predicate_private__` - karty wyłącznie na prywatnych tablicach", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - gdzie *<field>* to jeden z `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` lub `__predicate_member__`. Umieszczenie znaku `-` na początku *<field>* spowoduje wyszukanie elementów, w których brakuje wartości w tym polu (np. `ma:-data_wykonania` wyszuka karty, które nie mają ustawionej daty wykonania).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - gdzie *<sort-name>* jest jednym z `__predicate_due__`, `__predicate_created__` lub `__predicate_modified__`. Aby sortować malejąco, umieść znak `-` przed operatorem.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - gdzie *<n>* to liczba naturalna określająca liczbę kart wyświetlanych na stronie.", + "globalSearch-instructions-notes-1": "Można używać wielu kluczy w jednym zapytaniu.", + "globalSearch-instructions-notes-2": "Operatory tego samego typu są traktowane jako alternatywy. To znaczy, że zwracane są wszystkie karty, w których spełnione jest przynajmniej jedno z kryteriów.\n`__operator_list__:Dostępne __operator_list__:Wstrzymane` zwróci karty znajdujące się na listach o nazwach *Dostępne* i *Wstrzymane*.", + "globalSearch-instructions-notes-3": "Operatory różnego typu traktowane są jako suma logiczna (*AND*). Zwracane są tylko karty pasujące do wszystkich operatorów różnego typu. `__operator_list__:Dostępne __operator_label__:czerwona` zwróci tylko karty z listy *Dostępne*, które mają etykietę o barwie *czerwona*.", + "globalSearch-instructions-notes-3-2": "Dni można określać za pomocą liczby dodatniej lub ujemnej, albo za pomocą `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` lub `__predicate_year__` dla bieżącego okresu.", + "globalSearch-instructions-notes-4": "W kluczach wyszukiwania wielkie i małe litery można stosować zamiennie.", + "globalSearch-instructions-notes-5": "Domyślnie karty zarchiwizowane nie są przeszukiwane.", + "link-to-search": "Link do tego wyszukiwania", + "excel-font": "Arial", + "number": "Numer", + "label-colors": "Kolory etykiet", + "label-names": "Nazwy etykiet", + "archived-at": "zarchiwizowana", + "sort-cards": "Sortuj karty", + "cardsSortPopup-title": "Sortuj karty według", + "due-date": "terminu wykonania", + "server-error": "Błąd serwera", + "server-error-troubleshooting": "Uprzejmie proszę o przesłanie treści tego błędu serwera z logu.\nDla wersji snap uruchom: `sudo snap logs wekan.wekan`\nDla wersji Docker uruchom: `sudo docker logs wekan-app`", + "title-alphabetically": "nazwy (alfabetycznie)", + "created-at-newest-first": "daty utworzenia (najpierw najnowsze)", + "created-at-oldest-first": "daty utworzenia (najpierw najstarsze)", + "links-heading": "Linki", + "hide-system-messages-of-all-users": "Ukryj powiadomienia systemowe wszystkich użytkowników", + "now-system-messages-of-all-users-are-hidden": "Odtąd powiadomienia systemowe wszystkich użytkowników będą ukryte", + "move-swimlane": "Przenieś ścieżkę", + "moveSwimlanePopup-title": "Przenieś ścieżkę", + "custom-field-stringtemplate": "Wzór ciągu znaków", + "custom-field-stringtemplate-format": "Format (wstaw %{value} w miejsce zmiennych)", + "custom-field-stringtemplate-separator": "Separator (użyj lub   jako odstępu)", + "custom-field-stringtemplate-item-placeholder": "Wciśnij Enter, aby dodać więcej elementów", + "creator": "Twórca", + "filesReportTitle": "Wykaz plików", + "orphanedFilesReportTitle": "Wykaz osieroconych plików", + "reports": "Wykazy", + "rulesReportTitle": "Wykaz reguł", + "copy-swimlane": "Skopiuj ścieżkę", + "copySwimlanePopup-title": "Kopiowanie ścieżki", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json index b49e16c31..f6222abe1 100644 --- a/i18n/pt-BR.i18n.json +++ b/i18n/pt-BR.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "não-completada a lista de verificação %s", "activity-editComment": "comentário editado %s", "activity-deleteComment": "comentário excluído %s", + "activity-receivedDate": "editou recebido para %s de %s", + "activity-startDate": "editou data início para %s de %s", + "activity-dueDate": "editou prazo final para %s de %s", + "activity-endDate": "editou concluído para %s de %s", "add-attachment": "Adicionar Anexos", "add-board": "Adicionar Quadro", + "add-template": "Adicionar Modelo", "add-card": "Adicionar Cartão", + "add-card-to-top-of-list": "Adicionar Cartão no Topo da Lista", + "add-card-to-bottom-of-list": "Adicionar Cartão no Final da Lista", "add-swimlane": "Adicionar Raia", "add-subtask": "Adicionar subtarefa", "add-checklist": "Adicionar lista de verificação", @@ -113,6 +120,8 @@ "archives": "Arquivos morto", "template": "Modelo", "templates": "Modelos", + "template-container": "Contêiner de Modelo", + "add-template-container": "Adicionar Contêiner de Modelo", "assign-member": "Atribuir Membro", "attached": "anexado", "attachment": "Anexo", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Excluir Anexo?", "attachments": "Anexos", "auto-watch": "Veja automaticamente os boards que são criados", - "avatar-too-big": "O avatar é muito grande (70KB max)", + "avatar-too-big": "O avatar é muito grande (máximo: 520KB)", "back": "Voltar", "board-change-color": "Alterar cor", "board-nb-stars": "%s estrelas", "board-not-found": "Quadro não encontrado", "board-private-info": "Este quadro será <strong>privado</strong>.", "board-public-info": "Este quadro será <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Arraste e solte para reordenar os ícones do quadro. Clique no ícone do quadro para abri-lo. ", "boardChangeColorPopup-title": "Alterar Tela de Fundo", "boardChangeTitlePopup-title": "Renomear Quadro", "boardChangeVisibilityPopup-title": "Alterar Visibilidade", @@ -138,6 +148,7 @@ "board-view-cal": "Calendário", "board-view-swimlanes": "Raias", "board-view-collapse": "Expandir", + "board-view-gantt": "Gantt", "board-view-lists": "Listas", "bucket-example": "\"Bucket List\", por exemplo", "cancel": "Cancelar", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Anexar a partir de", "cardCustomField-datePopup-title": "Mudar data", "cardCustomFieldsPopup-title": "Editar campos customizados", + "cardStartVotingPopup-title": "Iniciar uma votação", + "positiveVoteMembersPopup-title": "Proponentes", + "negativeVoteMembersPopup-title": "Oponentes", + "card-edit-voting": "Editar votação", + "editVoteEndDatePopup-title": "Mudar votação e data", + "allowNonBoardMembers": "Permitir todos os usuários logados", + "vote-question": "Questão em votação", + "vote-public": "Mostrar quem votou no quê", + "vote-for-it": "a favor", + "vote-against": "contra", + "deleteVotePopup-title": "Excluir votação?", + "vote-delete-pop": "A exclusão é permanente. Você perderá todas as ações associadas a esta votação.", + "cardStartPlanningPokerPopup-title": "Iniciar um Planning Poker", + "card-edit-planning-poker": "Editar Planning Poker", + "editPokerEndDatePopup-title": "Mudar data final do Planning Poker", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Encerrar", + "poker-result-votes": "Votos", + "poker-result-who": "Quem", + "poker-replay": "Reiniciar", + "set-estimation": "Definir Estimativa", + "deletePokerPopup-title": "Excluir planning poker?", + "poker-delete-pop": "A exclusão é permanente. Você perderá todas as ações associadas a este planning poker.", "cardDeletePopup-title": "Excluir Cartão?", "cardDetailsActionsPopup-title": "Ações do cartão", "cardLabelsPopup-title": "Etiquetas", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Criar Modelo", "cards": "Cartões", "cards-count": "Cartões", + "cards-count-one": "Cartão", "casSignIn": "Entrar com CAS", "cardType-card": "Cartão", "cardType-linkedCard": "Cartão ligado", @@ -191,6 +236,7 @@ "close": "Fechar", "close-board": "Fechar Quadro", "close-board-pop": "Você será capaz de restaurar o quadro clicando no botão “Arquivo morto” a partir do cabeçalho do Início.", + "close-card": "Fechar Cartão", "color-black": "preto", "color-blue": "azul", "color-crimson": "carmesim", @@ -244,6 +290,8 @@ "current": "atual", "custom-field-delete-pop": "Não existe desfazer. Isso irá excluir o campo customizado de todos os cartões e destruir seu histórico", "custom-field-checkbox": "Caixa de seleção", + "custom-field-currency": "Moeda", + "custom-field-currency-option": "Código de moeda", "custom-field-date": "Data", "custom-field-dropdown": "Lista suspensa", "custom-field-dropdown-none": "(nada)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Você precisa ser um membro desse quadro para fazer isto", "error-json-malformed": "Seu texto não é um JSON válido", "error-json-schema": "Seu JSON não inclui as informações no formato correto", + "error-csv-schema": "Seu CSV(Comma Separated Values)/TSV (Tab Separated Values) não inclui a informação adequada no formato correto", "error-list-doesNotExist": "Esta lista não existe", "error-user-doesNotExist": "Este usuário não existe", "error-user-notAllowSelf": "Você não pode convidar a si mesmo", "error-user-notCreated": "Este usuário não foi criado", "error-username-taken": "Esse username já existe", + "error-orgname-taken": "Este nome de organização já está em uso ", + "error-teamname-taken": "Este nome de time já está em uso", "error-email-taken": "E-mail já está em uso", "export-board": "Exportar quadro", + "export-board-json": "Exportar quadro para JSON", + "export-board-csv": "Exportar quadro para CSV", + "export-board-tsv": "Exportar quadro para TSV", + "export-board-excel": "Exportar quadro para Excel", + "user-can-not-export-excel": "Usuário não pode exportar Excel", + "export-board-html": "Exportar quadro para HTML", + "export-card": "Exportar cartão", + "export-card-pdf": "Exportar cartão para PDF", + "user-can-not-export-card-to-pdf": "Usuário não pode exportar cartão para PDF", + "exportBoardPopup-title": "Exportar quadro", + "exportCardPopup-title": "Exportar cartão", "sort": "Ordenar", "sort-desc": "Clique para Ordenar Lista", "list-sort-by": "Ordenar a Lista por:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filtrar", "filter-cards": "Filtrar Cartões ou Listas", + "filter-dates-label": "Filtrar por data", + "filter-no-due-date": "Sem prazo final", + "filter-overdue": "Atrasado", + "filter-due-today": "Para hoje", + "filter-due-this-week": "Para nesta semana", + "filter-due-tomorrow": "Para amanhã", "list-filter-label": "Filtrar Lista por Título", "filter-clear": "Limpar filtro", + "filter-labels-label": "Filtrar por etiqueta", "filter-no-label": "Sem etiquetas", + "filter-member-label": "Filtrar por membro", "filter-no-member": "Sem membros", + "filter-assignee-label": "Filtrar por administrador", + "filter-no-assignee": "Não atribuído", + "filter-custom-fields-label": "Filtrar por campos customizados", "filter-no-custom-fields": "Não há campos customizados", "filter-show-archive": "Mostrar listas arquivadas", "filter-hide-empty": "Esconder listas vazias", "filter-on": "Filtro está ativo", "filter-on-desc": "Você está filtrando cartões neste quadro. Clique aqui para editar o filtro.", "filter-to-selection": "Filtrar esta seleção", + "other-filters-label": "Outros filtros", "advanced-filter-label": "Filtro avançado", "advanced-filter-description": "Filtros avançados permitem escrever uma \"string\" contendo os seguintes operadores: == != <= >= && || (). Um espaco é utilizado como separador entre os operadores. Você pode filtrar para todos os campos personalizados escrevendo os nomes e valores. Exemplo: Campo1 == Valor1. Nota^Se o campo ou valor tiver espaços você precisa encapsular eles em citações sozinhas. Exemplo: Campo1 == Eu\\sou. Também você pode combinar múltiplas condições. Exemplo: C1 == V1 || C1 == V2. Normalmente todos os operadores são interpretados da esquerda para direita. Você pode alterar a ordem colocando parênteses - como ma expressão matemática. Exemplo: C1 == V1 && (C2 == V2 || C2 == V3). Você tamb~em pode pesquisar campos de texto usando regex: C1 == /Tes.*/i", "fullname": "Nome Completo", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Criar Quadro", "home": "Início", "import": "Importar", + "impersonate-user": "Logar como", "link": "Ligação", "import-board": "importar quadro", "import-board-c": "Importar quadro", "import-board-title-trello": "Importar quadro do Trello", "import-board-title-wekan": "Importar quadro a partir de exportação prévia", - "import-sandstorm-backup-warning": "Não exclua os dados importados do quadro original exportado ou do Trello antes de verificar se esse item fecha e abre novamente, ou se você receber o erro Quadro não encontrado, que significa perda de dados.", - "import-sandstorm-warning": "O quadro importado irá excluir todos os dados existentes no quadro e irá sobrescrever com o quadro importado.", + "import-board-title-csv": "Importar quadro de CSV/TSV", "from-trello": "Do Trello", "from-wekan": "A partir de exportação prévia", + "from-csv": "De CSV/TSV", "import-board-instruction-trello": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', então copie o texto emitido", + "import-board-instruction-csv": "Cole seu Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "Em seu quadro vá para 'Menu', depois 'Exportar quadro' e copie o texto no arquivo baixado.", "import-board-instruction-about-errors": "Se você receber erros ao importar o quadro, às vezes a importação ainda funciona e o quadro está na página Todos os Quadros.", "import-json-placeholder": "Cole seus dados JSON válidos aqui", + "import-csv-placeholder": "Cole aqui os dados válidos de seu CSV/TSV", "import-map-members": "Mapear membros", "import-members-map": "Seu quadro importado possui alguns membros. Por favor, mapeie os membros que você deseja importar para seus usuários", + "import-members-map-note": "Nota: Membros não mapeados serão atribuídos ao usuário atual.", "import-show-user-mapping": "Revisar mapeamento dos membros", "import-user-select": "Escolha um usuário existente que você deseja usar como esse membro", "importMapMembersAddPopup-title": "Selecione membro", @@ -375,9 +453,13 @@ "list-select-cards": "Selecionar todos os cartões nesta lista", "set-color-list": "Definir Cor", "listActionPopup-title": "Listar Ações", + "settingsUserPopup-title": "Configurações do usuário", + "settingsTeamPopup-title": "Configurações do Time", + "settingsOrgPopup-title": "Configurações da Organização", "swimlaneActionPopup-title": "Ações de Raia", "swimlaneAddPopup-title": "Adicionar uma Raia abaixo", "listImportCardPopup-title": "Importe um cartão do Trello", + "listImportCardsTsvPopup-title": "Importar Excel CSV/TSV", "listMorePopup-title": "Mais", "link-list": "Vincular a esta lista", "list-delete-pop": "Todas as ações serão excluidas da lista de atividades e você não poderá recuperar a lista. Não há como desfazer.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Mover para o topo", "moveSelectionPopup-title": "Mover seleção", "multi-selection": "Multi-Seleção", + "multi-selection-label": "Definir etiqueta para a seleção", + "multi-selection-member": "Definir membro para a seleção", "multi-selection-on": "Multi-seleção está ativo", "muted": "Silenciar", "muted-info": "Você nunca receberá qualquer notificação desse board", @@ -441,8 +525,9 @@ "search": "Buscar", "rules": "Regras", "search-cards": "Buscar por título, descrição e campos customizados de cartão/lista neste quadro", - "search-example": "Texto para procurar", + "search-example": "Digite o que você procura e pressione Enter", "select-color": "Selecionar Cor", + "select-board": "Selecionar Quadro", "set-wip-limit-value": "Defina um limite máximo para o número de tarefas nesta lista", "setWipLimitPopup-title": "Definir Limite WIP", "shortcut-assign-self": "Atribuir a si o cartão atual", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtrar meus cartões", "shortcut-show-shortcuts": "Mostrar lista de atalhos", "shortcut-toggle-filterbar": "Alternar barra de filtro", + "shortcut-toggle-searchbar": "Alternar barra de busca", "shortcut-toggle-sidebar": "Fechar barra lateral.", "show-cards-minimum-count": "Mostrar contador de cards se a lista tiver mais de", "sidebar-open": "Abrir barra lateral", @@ -481,7 +567,15 @@ "upload": "Carregar", "upload-avatar": "Carregar um avatar", "uploaded-avatar": "Avatar carregado", + "custom-top-left-corner-logo-image-url": "URL da imagem do logo customizado do canto superior esquerdo", + "custom-top-left-corner-logo-link-url": "URL do link do logo customizado do canto superior esquerdo", + "custom-top-left-corner-logo-height": "Altura da imagem do logo customizado do canto superior esquerdo. Padrão: 27", + "custom-login-logo-image-url": "URL da Imagem do logo customizado de login", + "custom-login-logo-link-url": "URL do link do logo customizado de login", + "text-below-custom-login-logo": "Texto abaixo do logo customizado de login", + "automatic-linked-url-schemes": "Esquemas de URL personalizados que devem ser clicáveis automaticamente. Um esquema de URL por linha", "username": "Nome de usuário", + "import-usernames": "Importar nome de usuários", "view-it": "Visualizar", "warn-list-archived": "aviso: este cartão está em uma lista no Arquivo-texto", "watch": "Observar", @@ -553,7 +647,8 @@ "minutes": "minutos", "seconds": "segundos", "show-field-on-card": "Mostrar este campo no cartão", - "automatically-field-on-card": "Criar campo automaticamente para todos os cartões", + "automatically-field-on-card": "Adicionar campo aos novos cartões", + "always-field-on-card": "Adicionar campo a todos os cartões", "showLabel-field-on-card": "Mostrar etiqueta do campo no minicartão", "yes": "Sim", "no": "Não", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Permitir Mudança de e-mail", "accounts-allowUserNameChange": "Permitir alteração de nome de usuário", "createdAt": "Criado em", + "modifiedAt": "Modificado em", "verified": "Verificado", "active": "Ativo", "card-received": "Recebido", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Escolha uma cor", "assigned-by": "Atribuído por", "requested-by": "Solicitado por", + "card-sorting-by-number": "Ordenar cartões por número", "board-delete-notice": "Excluir é permanente. Você perderá todas as listas, cartões e ações associados nesse quadro.", "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e atividades serão excluídas e você não poderá recuperar o conteúdo do quadro. Não há como desfazer.", "boardDeletePopup-title": "Excluir quadro?", @@ -614,13 +711,16 @@ "r-delete-rule": "Excluir regra", "r-new-rule-name": "Título da nova regra", "r-no-rules": "Sem regras", + "r-trigger": "Gatilho", + "r-action": "Ação", "r-when-a-card": "Quando um cartão", "r-is": "é", "r-is-moved": "é movido", - "r-added-to": "adicionado à", + "r-added-to": "adicionado a", "r-removed-from": "Removido de", "r-the-board": "o quadro", "r-list": "lista", + "list": "Lista", "set-filter": "Inserir Filtro", "r-moved-to": "Movido para", "r-moved-from": "Movido de", @@ -665,6 +765,7 @@ "r-of-checklist": "da lista de verificação", "r-send-email": "Enviar um e-mail", "r-to": "para", + "r-of": "de", "r-subject": "assunto", "r-rule-details": "Detalhes da regra", "r-d-move-to-top-gen": "Mover cartão para o topo da sua lista", @@ -725,6 +826,8 @@ "display-authentication-method": "Mostrar Método de Autenticação", "default-authentication-method": "Método de Autenticação Padrão", "duplicate-board": "Duplicar Quadro", + "org-number": "O número de organizações é:", + "team-number": "O número de times é:", "people-number": "O número de pessoas é:", "swimlaneDeletePopup-title": "Excluir Raia?", "swimlane-delete-pop": "Todas as ações serão excluídas da lista de atividades e você não poderá recuperar a raia. Não há como desfazer.", @@ -750,6 +853,8 @@ "act-duenow": "está lembrando que o prazo final (__timeValue__) do __card__ é agora", "act-atUserComment": "Você foi mencionado no [__board__] __list__/__card__", "delete-user-confirm-popup": "Você realmente quer apagar esta conta? Não há como desfazer.", + "delete-team-confirm-popup": "Você tem certeza que quer excluir este time? Não há como desfazer.", + "delete-org-confirm-popup": "Você tem certeza que quer excluir esta organização? Não há como desfazer.", "accounts-allowUserDelete": "Permitir que usuários apaguem a própria conta", "hide-minicard-label-text": "Esconder rótulo da etiqueta do mini cartão", "show-desktop-drag-handles": "Mostrar alças de arrasto da área de trabalho", @@ -758,12 +863,200 @@ "addmore-detail": "Adicionar descrição detalhada", "show-on-card": "Mostrar no Cartão", "new": "Novo", + "editOrgPopup-title": "Editar Organização", + "newOrgPopup-title": "Nova Organização", + "editTeamPopup-title": "Editar Time", + "newTeamPopup-title": "Novo Time", "editUserPopup-title": "Editar usuário", "newUserPopup-title": "Novo usuário", "notifications": "Notificações", "view-all": "Ver tudo", "filter-by-unread": "Filtrar não lidas", "mark-all-as-read": "Marcar todas como lidas", + "remove-all-read": "Remover todas lidas", "allow-rename": "Permitir renomear", - "allowRenamePopup-title": "Permitir renomear" + "allowRenamePopup-title": "Permitir renomear", + "start-day-of-week": "Definir dia em que a semana começa", + "monday": "Segunda", + "tuesday": "Terça", + "wednesday": "Quarta", + "thursday": "Quinta", + "friday": "Sexta", + "saturday": "Sábado", + "sunday": "Domingo", + "status": "Status", + "swimlane": "Raia", + "owner": "Proprietário", + "last-modified-at": "Última modificação em", + "last-activity": "Última atividade", + "voting": "Votação", + "archived": "Arquivado", + "delete-linked-card-before-this-card": "Você não pode excluir este cartão antes de excluir primeiro o cartão vinculado que possui", + "delete-linked-cards-before-this-list": "Você não pode excluir esta lista antes de excluir primeiro os cartões vinculados que estão apontando para os cartões nesta lista", + "hide-checked-items": "Esconder itens marcados", + "task": "Tarefa", + "create-task": "Criar Tarefa", + "ok": "OK", + "organizations": "Organizações", + "teams": "Times", + "displayName": "Nome em exibição", + "shortName": "Nome curto", + "website": "Website", + "person": "Pessoa", + "my-cards": "Meus Cartões", + "card": "Cartão", + "board": "Quadro", + "context-separator": "/", + "myCardsSortChange-title": "Ordenar Meus Cartões", + "myCardsSortChangePopup-title": "Ordenar Meus Cartões", + "myCardsSortChange-choice-board": "Por Quadro", + "myCardsSortChange-choice-dueat": "Por Prazo Final", + "dueCards-title": "Cartões com prazo final", + "dueCardsViewChange-title": "Visão de Cartões com prazo final", + "dueCardsViewChangePopup-title": "Visão de Cartões com prazo final", + "dueCardsViewChange-choice-me": "Eu", + "dueCardsViewChange-choice-all": "Todos os usuários", + "dueCardsViewChange-choice-all-description": "Mostrar todos os cartões incompletos com *Prazo Final* nos quadros em que o usuário tem permissão", + "broken-cards": "Cartões quebrados", + "board-title-not-found": "Quadro '%s' não encontrado.", + "swimlane-title-not-found": "Raia '%s' não encontrada.", + "list-title-not-found": "Lista '%s' não encontrada.", + "label-not-found": "Etiqueta '%s' não encontrada.", + "label-color-not-found": "Cor de etiqueta %s não encontrada.", + "user-username-not-found": "Nome de usuário '%s' não encontrado.", + "comment-not-found": "Cartões com comentários contendo o texto '%s' não encontrado.", + "globalSearch-title": "Pesquisar em todos os quadros", + "no-cards-found": "Nenhum cartão encontrado", + "one-card-found": "Um cartão encontrado", + "n-cards-found": "%s cartões encontrados", + "n-n-of-n-cards-found": "__start__-__end__ de __total__ cartões encontrados", + "operator-board": "quadro", + "operator-board-abbrev": "q", + "operator-swimlane": "raia", + "operator-swimlane-abbrev": "r", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "etiqueta", + "operator-label-abbrev": "#", + "operator-user": "usuário", + "operator-user-abbrev": "@", + "operator-member": "membro", + "operator-member-abbrev": "m", + "operator-assignee": "administrador", + "operator-assignee-abbrev": "a", + "operator-creator": "criador", + "operator-status": "status", + "operator-due": "prazo final", + "operator-created": "criado", + "operator-modified": "modificado", + "operator-sort": "ordenar", + "operator-comment": "comentário", + "operator-has": "tem", + "operator-limit": "limite", + "predicate-archived": "arquivado", + "predicate-open": "aberta", + "predicate-ended": "finalizado", + "predicate-all": "todos", + "predicate-overdue": "atrasado", + "predicate-week": "semana", + "predicate-month": "mês", + "predicate-quarter": "trimestre", + "predicate-year": "ano", + "predicate-due": "prazo final", + "predicate-modified": "modificado", + "predicate-created": "criado", + "predicate-attachment": "anexo", + "predicate-description": "descrição", + "predicate-checklist": "lista de verificação", + "predicate-start": "início", + "predicate-end": "concluído", + "predicate-assignee": "administrador", + "predicate-member": "membro", + "predicate-public": "público", + "predicate-private": "privado", + "operator-unknown-error": "%s não é um operador", + "operator-number-expected": "operador __operator__ esperava um número, obteve '__value__'", + "operator-sort-invalid": "ordenar de '%s' é inválido", + "operator-status-invalid": "'%s' não é um status válido", + "operator-has-invalid": "%s não é uma verificação de existência válida", + "operator-limit-invalid": "%s não é um limite válido. Limite deve ser um número inteiro positivo.", + "next-page": "Próxima página", + "previous-page": "Página anterior", + "heading-notes": "Notas", + "globalSearch-instructions-heading": "Buscar instruções", + "globalSearch-instructions-description": "Buscas podem incluir operadores para otimizar a consulta. Operadores são especificados para serem escritos com o nome do operador e o seu valor separados por sinal de dois pontos. Por exemplo, uma especificação de operador para `list:Blocked` poderia limitar a busca a cartões que estão em uma lista chamada *Blocked*. Se o valor contém espaços ou caracteres especiais deverá ser colocado entre aspas (por exemplo, `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Operadores disponíveis:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cartões em quadros correspondente ao especificado *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cartões em listas correspondente ao especificado *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cartões em raias correspondente ao especificado *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cartões com um comentário contendo *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cartões que possuem uma etiqueta correspondente a *<color>* ou *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - abreviação para `__operator_label__:<color>` ou `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cartões onde *<username>* é um *member* ou *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - abreviação para `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cartões onde *<username>* é um *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cartões onde *<username>* é um *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cartões onde *<username>* é o criador do cartão", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cartões que vencem até *<n>* dias a partir de agora. `__operator_due__:__predicate_overdue__ lista todos os cartões após a data de vencimento.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cartões que foram criados *<n>* dias atrás ou menos", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cartões que foram modificados *<n>* dias atrás ou menos", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - onde *<status>* é um dos seguidores:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - cartões arquivados", + "globalSearch-instructions-status-all": "`__predicate_all__` - todos cartões arquivados e não arquivados", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cartões com uma data de conclusão", + "globalSearch-instructions-status-public": "`__predicate_public__` - apenas cartões em quadros públicos", + "globalSearch-instructions-status-private": "`__predicate_private__` - apenas cartões em quadros privados", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - onde *<field>* é um dos `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` ou `__predicate_member__`. Colocando um `-` em frente *<field>* pesquisa a ausência de um valor nesse campo (por exemplo, `has: -due` pesquisa cartões sem prazo final).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - onde *<sort-name>* é um dos `__predicate_due__`, `__predicate_created__` ou `__predicate_modified__`. Para uma classificação decrescente, coloque um `-` na frente do nome da classificação.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - onde *<n>* é um número inteiro positivo que expressa o número de cartões a serem exibidos por página.", + "globalSearch-instructions-notes-1": "Operadores múltiplos podem ser especificados", + "globalSearch-instructions-notes-2": "Operadores similares são *OR*, ou seja, do tipo \"Ou\". Cartões que correspondam a qualquer uma das condições será retornado.\n`__operator_list__:Available __operator_list__:Blocked` poderá retornar cartões que contém uma lista chamada *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Operadores de diferenciação são *AND*, ou seja, to tipo \"E. Apenas cartões que correspondam a todos os operadores de diferenciação são retornados. `__operator_list__:Available __operator_label__:red` retorna apenas os cartões na lista *Available* com uma etiqueta *red*.", + "globalSearch-instructions-notes-3-2": "Dias podem ser especificados como um inteiro positivo ou negativo ou usando `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` ou `__predicate_year__` para o período atual.", + "globalSearch-instructions-notes-4": "Textos de busca não distinguem maiúsculas e minúsculas", + "globalSearch-instructions-notes-5": "Por padrão, cartões arquivados não são pesquisados.", + "link-to-search": "Link para esta busca", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Cores de etiqueta", + "label-names": "Nomes de etiqueta", + "archived-at": "arquivado em", + "sort-cards": "Ordenar Cartões", + "cardsSortPopup-title": "Ordenar Cartões", + "due-date": "Prazo Final", + "server-error": "Erro do Servidor", + "server-error-troubleshooting": "Por favor, envie o erro gerado pelo servidor.\nPara uma instalação Snap, execute: `sudo snap logs wekan.wekan`\nPara uma instalação Docker, execute: `sudo docker logs wekan-app`", + "title-alphabetically": "Título (alfabeticamente) ", + "created-at-newest-first": "Criado em (o mais recente primeiro) ", + "created-at-oldest-first": "Criado em (o mais antigo primeiro) ", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Esconder mensagens do sistema para todos os usuários", + "now-system-messages-of-all-users-are-hidden": "Agora as mensagens do sistema para todos os usuários estão escondidas ", + "move-swimlane": "Mover Raia", + "moveSwimlanePopup-title": "Mover Raia", + "custom-field-stringtemplate": "Template de String", + "custom-field-stringtemplate-format": "Formato (use %{valor} como marcador de posição)", + "custom-field-stringtemplate-separator": "Separador (use ou   para um espaço)", + "custom-field-stringtemplate-item-placeholder": "Pressione enter para adicionar mais itens", + "creator": "Criador", + "filesReportTitle": "Relatório de Arquivos", + "orphanedFilesReportTitle": "Relatório de Arquivos Órfãos", + "reports": "Relatórios", + "rulesReportTitle": "Regras de Relatório", + "copy-swimlane": "Copiar Raia", + "copySwimlanePopup-title": "Copiar Raia", + "display-card-creator": "Exibir Criador do Cartão", + "wait-spinner": "Spinner de carregamento", + "Bounce": "Salto", + "Cube": "Cubo", + "Cube-Grid": "Grade de cubos", + "Dot": "Pontos", + "Double-Bounce": "Salto duplo", + "Rotateplane": "Rotação plana", + "Scaleout": "Dimensionamento", + "Wave": "Ondas", + "maximize-card": "Maximizar Cartão", + "minimize-card": "Minimizar Cartão", + "delete-org-warning-message": "Não é possível excluir esta organização. Existe pelo menos um usuário que pertence a ela.", + "delete-team-warning-message": "Não é possível excluir este time. Existe pelo menos um usuário que pertence a ele." } \ No newline at end of file diff --git a/i18n/pt.i18n.json b/i18n/pt.i18n.json index 97637b1e3..865bb9b25 100644 --- a/i18n/pt.i18n.json +++ b/i18n/pt.i18n.json @@ -1,6 +1,6 @@ { "accept": "Aceitar", - "act-activity-notify": "Notificação de Actividade", + "act-activity-notify": "Notificação de Atividade", "act-addAttachment": "adicionou o anexo __attachment__ ao cartão __card__ na lista __list__ na pista __swimlane__ no quadro __board__", "act-deleteAttachment": "apagou o anexo __attachment__ do cartão __card__ na lista __list__ na pista __swimlane__ no quadro __board__", "act-addSubtask": "adicionou a sub-tarefa __subtask__ ao cartão __card__ na lista __list__ na pista __swimlane__ no quadro __board__", @@ -42,9 +42,9 @@ "act-unjoinMember": "removeu o membro __member__ do cartão __card__ na lista __list__ na pista __swimlane__ no quadro __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", - "actions": "Acções", + "actions": "Ações", "activities": "Actividades", - "activity": "Actividade", + "activity": "Atividade", "activity-added": "adicionou %s a %s", "activity-archived": "%s foi movido para o Arquivo", "activity-attached": "anexou %s a %s", @@ -64,7 +64,7 @@ "activity-unchecked-item": "desmarcou %s na lista de verificação %s de %s", "activity-checklist-added": "adicionou a lista de verificação a %s", "activity-checklist-removed": "removeu a lista de verificação de %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "completou a lista de verificação %s de %s", "activity-checklist-uncompleted": "descompletou a lista de verificação %s de %s", "activity-checklist-item-added": "adicionou o item a '%s' em %s", "activity-checklist-item-removed": "removeu o item de '%s' na %s", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "descompletou a lista de verificação %s", "activity-editComment": "editou o comentário %s", "activity-deleteComment": "apagou o comentário %s", + "activity-receivedDate": "editou a data recebida para %s de %s", + "activity-startDate": "editou a data de início para %s de %s", + "activity-dueDate": "editou a data limite para %s de %s", + "activity-endDate": "editou a data de fim para %s de %s", "add-attachment": "Adicionar Anexo", "add-board": "Adicionar Quadro", + "add-template": "Add Template", "add-card": "Adicionar Cartão", + "add-card-to-top-of-list": "Adicionar Cartão no Topo da Lista", + "add-card-to-bottom-of-list": "Adicionar Cartão no Fundo da Lista", "add-swimlane": "Adicionar Pista", "add-subtask": "Adicionar Sub-tarefa", "add-checklist": "Adicionar Lista de Verificação", @@ -113,20 +120,23 @@ "archives": "Arquivo", "template": "Modelo", "templates": "Modelos", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Atribuir Membro", "attached": "anexado", "attachment": "Anexo", - "attachment-delete-pop": "Apagar um anexo é permanente. Não será possível recuperá-lo.", + "attachment-delete-pop": "Apagar um anexo é permanente. Não é reversível.", "attachmentDeletePopup-title": "Apagar Anexo?", "attachments": "Anexos", "auto-watch": "Observar automaticamente os quadros quando são criados", - "avatar-too-big": "O avatar é muito grande (70KB máx)", + "avatar-too-big": "O avatar é demasiado grande (520KB máx.)", "back": "Voltar", "board-change-color": "Alterar cor", "board-nb-stars": "%s estrelas", "board-not-found": "Quadro não encontrado", "board-private-info": "Este quadro será <strong>privado</strong>.", "board-public-info": "Este quadro será <strong>público</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Alterar Imagem de Fundo do Quadro", "boardChangeTitlePopup-title": "Renomear Quadro", "boardChangeVisibilityPopup-title": "Alterar Visibilidade", @@ -137,7 +147,8 @@ "board-view": "Visão do Quadro", "board-view-cal": "Calendário", "board-view-swimlanes": "Pistas", - "board-view-collapse": "Collapse", + "board-view-collapse": "Colapsar ", + "board-view-gantt": "Gantt", "board-view-lists": "Listas", "bucket-example": "\"Lista de Desejos\", por exemplo", "cancel": "Cancelar", @@ -145,7 +156,7 @@ "board-archived": "Este quadro está no Arquivo.", "card-comments-title": "Este cartão possui %s comentário.", "card-delete-notice": "A remoção será permanente. Perderá todas as acções associadas a este cartão.", - "card-delete-pop": "Todas as acções serão removidas do feed de Actividade e não poderá reabrir o cartão. Não há como desfazer.", + "card-delete-pop": "Todas as acções serão removidas do feed de atividade e não poderá reabrir o cartão. Não é reversível.", "card-delete-suggest-archive": "Pode mover um cartão para o Arquivo para removê-lo do quadro e preservar a atividade.", "card-due": "Data limite", "card-due-on": "Data limite em", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Anexar a partir de", "cardCustomField-datePopup-title": "Alterar a data", "cardCustomFieldsPopup-title": "Editar campos personalizados", + "cardStartVotingPopup-title": "Iniciar uma votação", + "positiveVoteMembersPopup-title": "Proponentes", + "negativeVoteMembersPopup-title": "Oponentes", + "card-edit-voting": "Alterar votação", + "editVoteEndDatePopup-title": "Alterar data de fim da votação", + "allowNonBoardMembers": "Permitir todos os utilizadores com sessão iniciada", + "vote-question": "Questão da votação", + "vote-public": "Mostrar quem votou o quê", + "vote-for-it": "a favor", + "vote-against": "contra", + "deleteVotePopup-title": "Apagar votação?", + "vote-delete-pop": "Apagar é permanente. Irá perder todas as ações associadas a este voto.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votos", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Definir Estimativa", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Apagar Cartão?", "cardDetailsActionsPopup-title": "Acções do Cartão", "cardLabelsPopup-title": "Etiquetas", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Criar Modelo", "cards": "Cartões", "cards-count": "Cartões", + "cards-count-one": "Cartão", "casSignIn": "Entrar com CAS", "cardType-card": "Cartão", "cardType-linkedCard": "Cartão Ligado", @@ -191,6 +236,7 @@ "close": "Fechar", "close-board": "Fechar o Quadro", "close-board-pop": "Poderá restaurar o quadro clicando no botão “Arquivo” a partir do cabeçalho do Início.", + "close-card": "Fechar Cartão", "color-black": "preto", "color-blue": "azul", "color-crimson": "carmesim", @@ -223,8 +269,8 @@ "comment-only-desc": "Pode comentar apenas em cartões.", "no-comments": "Sem comentários", "no-comments-desc": "Não pode ver comentários nem actividades.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker": "Trabalhador", + "worker-desc": "Apenas pode mover cartões, atribuir-se a si próprio ao cartão e comentar.", "computer": "Computador", "confirm-subtask-delete-dialog": "Tem certeza que deseja apagar a sub-tarefa?", "confirm-checklist-delete-dialog": "Tem certeza que quer apagar a lista de verificação?", @@ -242,8 +288,10 @@ "createCustomField": "Criar Campo", "createCustomFieldPopup-title": "Criar Campo", "current": "actual", - "custom-field-delete-pop": "Não existe desfazer. Isto irá remover este campo personalizado de todos os cartões e destruir o seu histórico", + "custom-field-delete-pop": "Não é reversível. Isto irá remover este campo personalizado de todos os cartões e destruir o seu histórico.", "custom-field-checkbox": "Caixa de selecção", + "custom-field-currency": "Moeda", + "custom-field-currency-option": "Código de Moeda", "custom-field-date": "Data", "custom-field-dropdown": "Lista Suspensa", "custom-field-dropdown-none": "(nada)", @@ -297,34 +345,60 @@ "error-board-notAMember": "Precisa de ser um membro deste quadro para fazer isso", "error-json-malformed": "O seu texto não é um JSON válido", "error-json-schema": "O seu JSON não inclui as informações apropriadas no formato correto", + "error-csv-schema": "O seu CSV(Comma Separated Values)/TSV (Tab Separated Values) não inclui a informação apropriada no formato correto", "error-list-doesNotExist": "Esta lista não existe", "error-user-doesNotExist": "Este utilizador não existe", "error-user-notAllowSelf": "Não se pode convidar a si mesmo", "error-user-notCreated": "Este utilizador não foi criado", "error-username-taken": "Esse nome de utilizador já existe", + "error-orgname-taken": "O nome desta organização já está em uso", + "error-teamname-taken": "Este nome de equipa já está em uso.", "error-email-taken": "Endereço de e-mail já está em uso", "export-board": "Exportar quadro", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", + "export-board-json": "Exportar quadro para JSON", + "export-board-csv": "Exportar quadro para CSV", + "export-board-tsv": "Exportar quadro para TSV", + "export-board-excel": "Exportar quadro para Excel", + "user-can-not-export-excel": "Utilizador não pode exportar Excel", + "export-board-html": "Exportar quadro para HTML", + "export-card": "Exportar cartão", + "export-card-pdf": "Exportar cartão para PDF", + "user-can-not-export-card-to-pdf": "Utilizador não pode exportar cartão para PDF", + "exportBoardPopup-title": "Exportar quadro", + "exportCardPopup-title": "Exportar cartão", + "sort": "Ordenar", + "sort-desc": "Clique para ordenar a Lista", + "list-sort-by": "Ordenar a Lista por:", + "list-label-modifiedAt": "Data do Último Acesso", + "list-label-title": "Nome da Lista", + "list-label-sort": "A Sua Ordem Manual", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Filtrar", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", + "filter-cards": "Filtrar Cartões ou Listas", + "filter-dates-label": "Filtrar por data", + "filter-no-due-date": "Sem data limite", + "filter-overdue": "Data limite ultrapassada", + "filter-due-today": "Data limite hoje", + "filter-due-this-week": "Data limite esta semana", + "filter-due-tomorrow": "Data limite amanhã", + "list-filter-label": "Filtrar Lista por Título", "filter-clear": "Limpar filtro", + "filter-labels-label": "Filtrar por etiqueta", "filter-no-label": "Sem etiquetas", + "filter-member-label": "Filtrar por membro", "filter-no-member": "Sem membros", + "filter-assignee-label": "Filtrar por responsável", + "filter-no-assignee": "Sem responsável", + "filter-custom-fields-label": "Filtrar por campos personalizados", "filter-no-custom-fields": "Sem Campos Personalizados", "filter-show-archive": "Mostrar listas arquivadas", "filter-hide-empty": "Ocultar listas vazias", "filter-on": "Filtro está activo", "filter-on-desc": "Está a filtrar cartões neste quadro. Clique aqui para editar o filtro.", "filter-to-selection": "Filtrar esta selecção", + "other-filters-label": "Outros filtros", "advanced-filter-label": "Filtro Avançado", "advanced-filter-description": "Filtro Avançado permite escrever uma \"string\" contendo os seguintes operadores: == != <= >= && || ( ). Um espaço é usado como separador entre Operadores. Pode filtrar em todos os Campos Personalizados escreventos os seus nomes e valores. Por Exemplo: Campo1 == Valor1. Nota: Se os campos ou valores contiverem espaços, tem de os encapsular em apóstrofes. Por Exemplo: 'Campo 1' == 'Valor 1'. Para que caracteres de controlo únicos (' \\/) sejam ignorados, pode usar \\. Por exemplo: Campo1 == I\\'m. Pode também combinar múltiplas condições. Por Exemplo: F1 == V1 || F1 == V2. Normalmente todos os operadores são interpretados da esquerda para a direita. Pode alterar a ordem inserindo parênteses. Por Exemplo: F1 == V1 && ( F2 == V2 || F2 == V3 ). Pode também procurar em campos de texto utilizando uma expressão regular: F1 == /Tes.*/i", "fullname": "Nome Completo", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Criar Quadro", "home": "Início", "import": "Importar", + "impersonate-user": "Personificar utilizador", "link": "Ligação", "import-board": "importar quadro", "import-board-c": "Importar quadro", "import-board-title-trello": "Importar quadro do Trello", "import-board-title-wekan": "Importar quadro a partir de exportação prévia", - "import-sandstorm-backup-warning": "Não apague os dados importados do quadro original exportado ou do Trello antes de verificar se esse item fecha e abre novamente, ou se receber o erro Quadro não encontrado, que significa perda de dados.", - "import-sandstorm-warning": "O quadro importado irá apagar todos os dados existentes no quadro e irá sobrescrever com o quadro importado.", + "import-board-title-csv": "Importar quadro de CSV/TSV", "from-trello": "Do Trello", "from-wekan": "A partir de exportação prévia", + "from-csv": "De CSV/TSV", "import-board-instruction-trello": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', e copie o texto resultante.", + "import-board-instruction-csv": "Cole o seu Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "No seu quadro vá para 'Menu', depois 'Exportar quadro' e copie o texto no ficheiro descarregado.", "import-board-instruction-about-errors": "Se receber erros ao importar o quadro, às vezes a importação ainda funciona e o quadro está na página Todos os Quadros.", "import-json-placeholder": "Cole seus dados JSON válidos aqui", + "import-csv-placeholder": "Copie aqui os seus dados CSV/TSV", "import-map-members": "Mapear membros", "import-members-map": "O seu quadro importado possui alguns membros. Por favor, mapeie os membros que deseja importar para seus utilizadores", + "import-members-map-note": "Nota: Membros não atribuídos serão associados ao utilizador atual.", "import-show-user-mapping": "Rever mapeamento dos membros", "import-user-select": "Escolha um utilizador existente que deseja usar como esse membro", "importMapMembersAddPopup-title": "Seleccione membro", @@ -361,7 +439,7 @@ "keyboard-shortcuts": "Atalhos do teclado", "label-create": "Criar Etiqueta", "label-default": "%s etiqueta (omissão)", - "label-delete-pop": "Não há como desfazer. A etiqueta será apagada de todos os cartões e o seu histórico será destruído.", + "label-delete-pop": "Não é reversível. Isto irá remover esta etiqueta de todos os cartões e destruir o seu histórico.", "labels": "Etiquetas", "language": "Idioma", "last-admin-desc": "Não pode alterar funções porque deve existir pelo menos um administrador.", @@ -375,12 +453,16 @@ "list-select-cards": "Seleccionar todos os cartões nesta lista", "set-color-list": "Definir Cor", "listActionPopup-title": "Listar Ações", + "settingsUserPopup-title": "Configurações do utilizador", + "settingsTeamPopup-title": "Definições Equipa", + "settingsOrgPopup-title": "Definições Organização", "swimlaneActionPopup-title": "Acções de Pista", "swimlaneAddPopup-title": "Adicionar uma Pista abaixo", "listImportCardPopup-title": "Importe um cartão do Trello", + "listImportCardsTsvPopup-title": "Importar Excel CSV/TSV", "listMorePopup-title": "Mais", "link-list": "Ligar a esta lista", - "list-delete-pop": "Todas as acções serão removidas do feed de actividade e não poderá recuperar a lista. Não há como desfazer.", + "list-delete-pop": "Todas as acções serão removidas do feed de atividade e não poderá recuperar a lista. Não é reversível.", "list-delete-suggest-archive": "Pode mover uma lista para o Arquivo para a remover do quadro e preservar a actividade.", "lists": "Listas", "swimlanes": "Pistas", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Mover para o Topo", "moveSelectionPopup-title": "Mover a selecção", "multi-selection": "Selecção Múltipla", + "multi-selection-label": "Definir etiqueta para seleção", + "multi-selection-member": "Defina membro para seleção", "multi-selection-on": "Selecção Múltipla está activa", "muted": "Silenciado", "muted-info": "Nunca será notificado de quaisquer alterações neste quadro", @@ -440,9 +524,10 @@ "save": "Guardar", "search": "Procurar", "rules": "Regras", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Texto a procurar?", + "search-cards": "Procurar nos títulos, descrições e campos personalizados de cartões/listas neste quadro", + "search-example": "Escreva o texto a pesquisar e pressione Enter", "select-color": "Seleccionar Cor", + "select-board": "Selecione Quadro", "set-wip-limit-value": "Defina um limite máximo para o número de tarefas nesta lista", "setWipLimitPopup-title": "Definir Limite WIP", "shortcut-assign-self": "Atribuir a si o cartão actual", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtrar os meus cartões", "shortcut-show-shortcuts": "Mostrar esta lista de atalhos", "shortcut-toggle-filterbar": "Alternar a Barra Lateral de Filtros", + "shortcut-toggle-searchbar": "Alternar a Barra Lateral de Pesquisa", "shortcut-toggle-sidebar": "Alternar a Barra Lateral do Quadro", "show-cards-minimum-count": "Mostrar contagem de cartões se a lista tiver mais de", "sidebar-open": "Abrir a Barra Lateral", @@ -481,7 +567,15 @@ "upload": "Enviar", "upload-avatar": "Enviar um avatar", "uploaded-avatar": "Enviado um avatar", + "custom-top-left-corner-logo-image-url": "URL da Imagem do Logo Personalizado do Canto Superior Esquerdo", + "custom-top-left-corner-logo-link-url": "URL do Link do Logo Personalizado do Canto Superior Esquerdo", + "custom-top-left-corner-logo-height": "Altura do Logo Personalizado do Canto Superior Esquerdo. Por omissão: 27", + "custom-login-logo-image-url": "URL da Imagem do Logo de Login Personalizado", + "custom-login-logo-link-url": "Link URL do Logo de Login Personalizado", + "text-below-custom-login-logo": "Texto abaixo do Logo de Login Personalizado", + "automatic-linked-url-schemes": "Esquema de URL personalizado que deve ser clicável automaticamente. Um Esquema de URL por linha", "username": "Nome de utilizador", + "import-usernames": "Importar Nomes de Utilizador", "view-it": "Visualizá-lo", "warn-list-archived": "aviso: este cartão está numa lista no Arquivo", "watch": "Observar", @@ -524,14 +618,14 @@ "email-smtp-test-text": "Enviou um e-mail com sucesso", "error-invitation-code-not-exist": "O código do convite não existe", "error-notAuthorized": "Não tem autorização para ver esta página.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", + "webhook-title": "Nome do Webhook", + "webhook-token": "Token (Opcional para Autenticação)", "outgoing-webhooks": "Webhooks de saída", - "bidirectional-webhooks": "Two-Way Webhooks", + "bidirectional-webhooks": "Webhooks de Dois Sentidos", "outgoingWebhooksPopup-title": "Webhooks de saída", "boardCardTitlePopup-title": "Filtro do Título do Cartão", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", + "disable-webhook": "Desativar Este Webhook", + "global-webhook": "Webhooks Globais", "new-outgoing-webhook": "Novo Webhook de saída", "no-name": "(Desconhecido)", "Node_version": "Versão do Node", @@ -553,7 +647,8 @@ "minutes": "minutos", "seconds": "segundos", "show-field-on-card": "Mostrar este campo no cartão", - "automatically-field-on-card": "Criar campo automaticamente para todos os cartões", + "automatically-field-on-card": "Adicionar campo aos novos cartões", + "always-field-on-card": "Adicionar campo a todos os cartões", "showLabel-field-on-card": "Mostrar etiqueta do campo no mini-cartão", "yes": "Sim", "no": "Não", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Permitir Alteração do E-mail", "accounts-allowUserNameChange": "Permitir Alteração de Nome de Utilizador", "createdAt": "Criado em", + "modifiedAt": "Modificado em", "verified": "Verificado", "active": "Activo", "card-received": "Recebido", @@ -575,17 +671,18 @@ "setListColorPopup-title": "Escolha uma cor", "assigned-by": "Atribuído Por", "requested-by": "Solicitado Por", + "card-sorting-by-number": "Ordenar cartões por número", "board-delete-notice": "Apagar é permanente. Irá perder todas as listas, cartões e acções associadas a este quadro.", - "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e actividades serão apagadas e não poderá recuperar o conteúdo do quadro. Não há como desfazer.", + "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e atividades serão apagadas e não poderá recuperar o conteúdo do quadro. Não é reversível.", "boardDeletePopup-title": "Apagar Quadro?", "delete-board": "Apagar Quadro", "default-subtasks-board": "Sub-tarefas para o quadro __board__", "default": "Omissão", "queue": "Fila", "subtask-settings": "Configurações de Sub-tarefas", - "card-settings": "Card Settings", + "card-settings": "Definições do Cartão", "boardSubtaskSettingsPopup-title": "Configurações das Sub-tarefas do Quadro", - "boardCardSettingsPopup-title": "Card Settings", + "boardCardSettingsPopup-title": "Definições do Cartão", "deposit-subtasks-board": "Depositar sub-tarefas neste quadro:", "deposit-subtasks-list": "Lista de destino para sub-tarefas depositadas aqui:", "show-parent-in-minicard": "Mostrar pai no mini-cartão:", @@ -614,13 +711,16 @@ "r-delete-rule": "Apagar regra", "r-new-rule-name": "Título da nova regra", "r-no-rules": "Sem regras", + "r-trigger": "Gatilho", + "r-action": "Ação", "r-when-a-card": "Quando um cartão", "r-is": "é", "r-is-moved": "é movido", - "r-added-to": "adicionado a", + "r-added-to": "Adicionado a", "r-removed-from": "Removido de", "r-the-board": "o quadro", "r-list": "lista", + "list": "Lista", "set-filter": "Definir Filtro", "r-moved-to": "Movido para", "r-moved-from": "Movido de", @@ -665,12 +765,13 @@ "r-of-checklist": "da lista de verificação", "r-send-email": "Enviar um e-mail", "r-to": "para", + "r-of": "de", "r-subject": "assunto", "r-rule-details": "Detalhes da regra", "r-d-move-to-top-gen": "Mover cartão para o topo da sua lista", "r-d-move-to-top-spec": "Mover cartão para o topo da lista", "r-d-move-to-bottom-gen": "Mover cartão para o fundo da sua lista", - "r-d-move-to-bottom-spec": "Mover cartão para fundo da lista", + "r-d-move-to-bottom-spec": "Mover cartão para o fundo da lista", "r-d-send-email": "Enviar e-mail", "r-d-send-email-to": "para", "r-d-send-email-subject": "assunto", @@ -725,9 +826,11 @@ "display-authentication-method": "Mostrar Método de Autenticação", "default-authentication-method": "Método de Autenticação por Omissão", "duplicate-board": "Duplicar Quadro", + "org-number": "O número de organizações é:", + "team-number": "O número de equipas é:", "people-number": "O número de pessoas é:", "swimlaneDeletePopup-title": "Apagar Pista ?", - "swimlane-delete-pop": "Todas as acções serão removidas do feed de actividade e não será possível recuperar a pista. Não há como desfazer.", + "swimlane-delete-pop": "Todas as acções serão removidas do feed de atividade e não será possível recuperar a pista. Não é reversível.", "restore-all": "Restaurar todos", "delete-all": "Apagar todos", "loading": "A carregar, por favor aguarde.", @@ -743,27 +846,217 @@ "almostdue": "a data limite actual %s está-se a aproximar", "pastdue": "a data limite actual %s já passou", "duenow": "a data limite actual %s é hoje", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-newDue": "__list__/__card__ tem o 1º lembrete de prazo [__board__]", + "act-withDue": "__list__/__card__ lembretes de prazo [__board__]", "act-almostdue": "estava a lembrar que a data limite actual (__timeValue__) de __card__ está-se a aproximar", "act-pastdue": "estava a lembrar que a data limite (__timeValue__) de __card__ já passou", "act-duenow": "estava a lembrar que a data limite (__timeValue__) de __card__ é agora", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Tem a certeza que pretende apagar esta conta? Não há como desfazer.", + "act-atUserComment": "Foi mencionado em [__board__] __list__/__card__", + "delete-user-confirm-popup": "Tem a certeza que pretende apagar esta conta? Não é reversível.", + "delete-team-confirm-popup": "Tem a certeza que pretende apagar esta equipa? Não é reversível.", + "delete-org-confirm-popup": "Tem a certeza que pretende apagar esta organização? Não é reversível.", "accounts-allowUserDelete": "Permitir aos utilizadores apagar as suas próprias contas", - "hide-minicard-label-text": "Ocultar texto das etiquetas dos mini-cartões", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "hide-minicard-label-text": "Ocultar texto da etiqueta dos mini-cartões", + "show-desktop-drag-handles": "Mostrar alças de arrasto do desktop", + "assignee": "Responsável", + "cardAssigneesPopup-title": "Responsável", + "addmore-detail": "Adicionar uma descrição mais detalhada", + "show-on-card": "Mostrar no Cartão", + "new": "Novo", + "editOrgPopup-title": "Editar Organização", + "newOrgPopup-title": "Nova Organização", + "editTeamPopup-title": "Editar Equipa", + "newTeamPopup-title": "Nova Equipa", + "editUserPopup-title": "Editar Utilizador", + "newUserPopup-title": "Novo Utilizador", + "notifications": "Notificações", + "view-all": "Ver Todos", + "filter-by-unread": "Filtrar por não lidos", + "mark-all-as-read": "Marcar todos como lidos", + "remove-all-read": "Apagar todos os lidos", + "allow-rename": "Permitir Renomear", + "allowRenamePopup-title": "Permitir Renomear", + "start-day-of-week": "Definir dia de início da semana", + "monday": "Segunda-feira", + "tuesday": "Terça-feira", + "wednesday": "Quarta-feira", + "thursday": "Quinta-feira", + "friday": "Sexta-feira", + "saturday": "Sábado", + "sunday": "Domingo", + "status": "Estado", + "swimlane": "Pista", + "owner": "Dono", + "last-modified-at": "Modificado pela última vez em", + "last-activity": "Última actividade", + "voting": "Votação", + "archived": "Arquivado", + "delete-linked-card-before-this-card": "Não pode apagar este cartão sem primeiro apagar o cartão ligado que tem", + "delete-linked-cards-before-this-list": "Não pode apagar esta lista sem primeiro apagar cartões ligados que apontam para cartões nesta lista", + "hide-checked-items": "Ocultar items marcados", + "task": "Tarefa", + "create-task": "Criar Tarefa", + "ok": "OK", + "organizations": "Organizações", + "teams": "Equipas", + "displayName": "Nome a mostrar", + "shortName": "Nome Curto", + "website": "Website", + "person": "Pessoa", + "my-cards": "Meus Cartões", + "card": "Cartão", + "board": "Quadro", + "context-separator": "/", + "myCardsSortChange-title": "Ordenar os meus Cartões", + "myCardsSortChangePopup-title": "Ordenar os meus Cartões", + "myCardsSortChange-choice-board": "Por Quadro", + "myCardsSortChange-choice-dueat": "Por Data Limite", + "dueCards-title": "Cartões Data Limite", + "dueCardsViewChange-title": "Vista Cartões Data Limite", + "dueCardsViewChangePopup-title": "Vista Cartões Data Limite", + "dueCardsViewChange-choice-me": "Eu", + "dueCardsViewChange-choice-all": "Todos os Utilizadores", + "dueCardsViewChange-choice-all-description": "Mostra todos os cartões incompletos com data *Limite* de quadros onde o utilizador tem permissões.", + "broken-cards": "Cartões Defeituosos", + "board-title-not-found": "Quadro '%s' não encontrado.", + "swimlane-title-not-found": "Pista '%s' não encontrada.", + "list-title-not-found": "Lista '%s' não encontrada.", + "label-not-found": "Etiqueta '%s' não encontrada.", + "label-color-not-found": "Cor de Etiqueta %s não encontrada", + "user-username-not-found": "Utilizador '%s' não encontrado.", + "comment-not-found": "Cartão com comentários com o texto '%s' não encontrado.", + "globalSearch-title": "Pesquisa Todos os Cartões", + "no-cards-found": "Sem Cartões Encontrados", + "one-card-found": "Um Cartão Encontrado", + "n-cards-found": "%s Cartões Encontrados", + "n-n-of-n-cards-found": "__start__-__end__ de __total__ Cartões Encontrados", + "operator-board": "quadro", + "operator-board-abbrev": "q", + "operator-swimlane": "pista", + "operator-swimlane-abbrev": "p", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "etiqueta", + "operator-label-abbrev": "#", + "operator-user": "utilizador", + "operator-user-abbrev": "@", + "operator-member": "membro", + "operator-member-abbrev": "m", + "operator-assignee": "responsável", + "operator-assignee-abbrev": "r", + "operator-creator": "criador", + "operator-status": "estado", + "operator-due": "data limite", + "operator-created": "criado", + "operator-modified": "modificado", + "operator-sort": "ordenar", + "operator-comment": "comentar", + "operator-has": "tem", + "operator-limit": "limite", + "predicate-archived": "arquivado", + "predicate-open": "aberto", + "predicate-ended": "terminou", + "predicate-all": "todos", + "predicate-overdue": "data limite ultrapassada", + "predicate-week": "semana", + "predicate-month": "mês", + "predicate-quarter": "trimestre", + "predicate-year": "ano", + "predicate-due": "data limite", + "predicate-modified": "modificado", + "predicate-created": "criado", + "predicate-attachment": "anexo", + "predicate-description": "descrição", + "predicate-checklist": "lista de verificação", + "predicate-start": "início", + "predicate-end": "fim", + "predicate-assignee": "responsável", + "predicate-member": "membro", + "predicate-public": "público", + "predicate-private": "privado", + "operator-unknown-error": "%s não é um operador", + "operator-number-expected": "operador __operator__ esperava um número, teve '__value__'", + "operator-sort-invalid": "ordenar por '%s' é inválido", + "operator-status-invalid": "'%s' não é um estado válido", + "operator-has-invalid": "%s não é uma verificação válida", + "operator-limit-invalid": "%s não é um limite válido. Deve ser um número positivo.", + "next-page": "Página Seguinte", + "previous-page": "Página Anterior", + "heading-notes": "Notas", + "globalSearch-instructions-heading": "Instruções de Pesquisa", + "globalSearch-instructions-description": "Pesquisas podem incluir operadores para afinar a pesquisa. Operadores são definidos por escrever o nome do operador e o valor separados por dois pontos. Por exemplo, um operador definido como 'list:Blocked' vai limitar a pesquisa a cartões que pertençam a uma lista chamada *Blocked*. Se o valor conter espaços ou caracteres especiais, eles devem ser envolvidos em aspas (e.x. `__operator_list__:\"A Rever\"`).", + "globalSearch-instructions-operators": "Operadores disponíveis:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cartões em quadros que respeitam o filtro *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cartões em listas que respeitam o filtro *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cartões em pistas que respeitam o filtro *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cartões com um comentário que contem *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cartões que tenham uma etiqueta igual a *<color>* ou *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - abreviatura de `__operator_label__:` ou `__operator_label__:`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cartões onde *<username>* é *member* ou *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - abreviatura para `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cartões onde *<username>* é um *membro*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cartões onde *<username>* é *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cartões onde *<username>* é o criador do cartão", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cartões que atingem a data limite em *<n>* dias a partir de hoje. `__operator_due__:__predicate_overdue__ lista todos os cartões que ultrapassaram as suas datas limite.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cartões que foram criados há *<n>* dias ou menos", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cartões que foram modificados há *<n>* dias ou menos", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - onde *<status>* é um dos seguintes:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - cartões arquivados", + "globalSearch-instructions-status-all": "`__predicate_all__` - todos os cartões arquivados e não arquivados", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cartões com uma data de fim", + "globalSearch-instructions-status-public": "`__predicate_public__` - cartões apenas em quadros públicos", + "globalSearch-instructions-status-private": "`__predicate_private__` - cartões apenas em quadros privados", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - onde *<field>* é um de `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` ou `__predicate_member__`. Ao colocar um `-` em frente a *<field>* pesquisa pela ausência de um valor naquele campo (ex. `has:-due` pesquisa por cartões sem uma data limite).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - onde *<sort-name>* é um de `__predicate_due__`, `__predicate_created__` ou `__predicate_modified__`. Para ordenação descendente, coloque um `-` em frente ao campo de ordenação.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - onde *<n>* é um número positivo que indica o número de cartões a mostrar por página.", + "globalSearch-instructions-notes-1": "Vários operadores podem ser definidos.", + "globalSearch-instructions-notes-2": "Operadores similares são unidos por *OU*. Cartões que igualem qualquer uma das condições serão devolvidos.\n`__operator_list__:Available __operator_list__:Blocked` deve devolver cartões de qualquer lista com o nome *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Diferentes operadores são unidos por *E*. Apenas os cartões que igualem todos os operadores serão devolvidos. `__operator_list__:Available __operator_label__:red` devolve apenas os cartões na lista *Available* com uma etiqueta *red*.", + "globalSearch-instructions-notes-3-2": "Dias podem ser indicados por números positivos ou negativos ou usando `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` ou `__predicate_year__` para o período atual.", + "globalSearch-instructions-notes-4": "Pesquisas de texto não diferenciam maiúsculas de minúsculas.", + "globalSearch-instructions-notes-5": "Por defeito os cartões arquivados não são pesquisados.", + "link-to-search": "Link para esta pesquisa", + "excel-font": "Arial", + "number": "Número", + "label-colors": "Cores de Etiqueta", + "label-names": "Nomes de Etiqueta", + "archived-at": "Arquivado em", + "sort-cards": "Ordenar Cartões", + "cardsSortPopup-title": "Ordenar Cartões", + "due-date": "Data Limite", + "server-error": "Erro no Servidor", + "server-error-troubleshooting": "Por favor submeta o erro gerado pelo servidor.\nPara uma instalação snap, execute: `sudo snap logs wekan.wekan`\nPara uma instalação Docker, execute: `sudo docker logs wekan-app`", + "title-alphabetically": "Título (Alfabeticamente)", + "created-at-newest-first": "Criado Em (Recentes Primeiro)", + "created-at-oldest-first": "Criado Em (Antigos Primeiro)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Omitir mensagens de sistema de todos os utilizadores", + "now-system-messages-of-all-users-are-hidden": "Todas as mensagens de sistema são omitidas a todos os utilizadores", + "move-swimlane": "Mover Pista", + "moveSwimlanePopup-title": "Mover Pista", + "custom-field-stringtemplate": "Texto Modelo", + "custom-field-stringtemplate-format": "Formato (use %{value} como substituto)", + "custom-field-stringtemplate-separator": "Separador (use ou   para um espaço)", + "custom-field-stringtemplate-item-placeholder": "Pressione enter para adicionar mais itens", + "creator": "Criador", + "filesReportTitle": "Relatório de Ficheiros", + "orphanedFilesReportTitle": "Relatórios de Ficheiros Soltos", + "reports": "Relatórios", + "rulesReportTitle": "Relatório de Regras", + "copy-swimlane": "Copiar Pista", + "copySwimlanePopup-title": "Copiar Pista", + "display-card-creator": "Mostrar Criador de Cartões", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximizar Cartão", + "minimize-card": "Minimizar Cartão", + "delete-org-warning-message": "Não pode apagar esta organização, tem no mínimo um utilizador associado", + "delete-team-warning-message": "Não pode apagar esta equipa, tem no mínimo um utilizador associado" } \ No newline at end of file diff --git a/i18n/ro.i18n.json b/i18n/ro.i18n.json index 43d4b8ecf..abb40c7f9 100644 --- a/i18n/ro.i18n.json +++ b/i18n/ro.i18n.json @@ -1,15 +1,15 @@ { "accept": "Aceptă", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-activity-notify": "Notificări de activitate", + "act-addAttachment": "atașament adăugat__atașament__pe card__card__în listă__listă__în culoar__culoar__pe tablă__tablă", + "act-deleteAttachment": "atașament șters__atașament__de pe card__card__din listă__listă__din culoar__culoar__de pe tablă__tabla", + "act-addSubtask": "subdiviziune sarcină adăugata__subdiviziune sarcină__pe card__card__în listă__listă__în culoar__culoar__pe tablă__tablă", + "act-addLabel": "etichetă adaugată__etichetă__pe card__card__în listă___listă__în culoar__culoar__pe tablă__tablă", + "act-addedLabel": "etichetă adaugată__etichetă__pe card__card__în listă___listă__în culoar__culoar__pe tablă__tablă", + "act-removeLabel": "etichetă îndepărtată__etichetă__de pe card__card__la listă__la culoar__culoar__la panou__panou", + "act-removedLabel": "etichetă îndepărtată__etichetă__de pe card__card__la listă__la culoar__culoar__la panou__panou", + "act-addChecklist": "listă de verificare adăugată__listă de verificare__pe card_card__la listă__listă__la culoar__culoar__la panou__panou", + "act-addChecklistItem": "element listă de verificare adăugat__elementlistădeverificare__în lista de verificare__listă de verificare__la card__card__la listă__listă__la culoar__culoar__la panou__panou", "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Atribuie unui membru", "attached": "s-a atașat", "attachment": "Ataşament", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Șterge Atașament?", "attachments": "Ataşamente", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Înapoi", "board-change-color": "Schimbă culoare", "board-nb-stars": "%s stele", "board-not-found": "Tabla nu a fost găsită", "board-private-info": "Această tabla va fi <strong>privată</strong>.", "board-public-info": "Această tabla va fi <strong>publică</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Schimbă Fundalul Tablei", "boardChangeTitlePopup-title": "Redenumește Tabla", "boardChangeVisibilityPopup-title": "Schimbă Vizibilitatea", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Liste", "bucket-example": "Like “Bucket List” for example", "cancel": "Anulează", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Închide", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Caută", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ru.i18n.json b/i18n/ru.i18n.json index 7baa92266..a71229e93 100644 --- a/i18n/ru.i18n.json +++ b/i18n/ru.i18n.json @@ -64,7 +64,7 @@ "activity-unchecked-item": "снял %s в контрольном списке %s в %s", "activity-checklist-added": "добавил контрольный список в %s", "activity-checklist-removed": "удалил контрольный список из %s", - "activity-checklist-completed": "завершил чек-лист %s в %s", + "activity-checklist-completed": "завершил контрольный список %s в %s", "activity-checklist-uncompleted": "вновь открыл контрольный список %s в %s", "activity-checklist-item-added": "добавил пункт в контрольный список '%s' в карточке %s", "activity-checklist-item-removed": "удалил пункт из контрольного списка '%s' в карточке %s", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "вновь открыл контрольный список %s", "activity-editComment": "отредактировал комментарий %s", "activity-deleteComment": "удалил комментарий %s", + "activity-receivedDate": "отредактировал дату получения на %sс %s", + "activity-startDate": "отредактировал дату начала на %sс %s", + "activity-dueDate": "отредактировал срок исполнения на %s с %s", + "activity-endDate": "отредактировал дату завершения на %s с %s", "add-attachment": "Добавить вложение", "add-board": "Добавить доску", + "add-template": "Добавить шаблон", "add-card": "Добавить карточку", + "add-card-to-top-of-list": "Добавить карточку в начало списка", + "add-card-to-bottom-of-list": "Добавить карточку в конец списка", "add-swimlane": "Добавить дорожку", "add-subtask": "Добавить подзадачу", "add-checklist": "Добавить контрольный список", @@ -113,6 +120,8 @@ "archives": "Архив", "template": "Шаблон", "templates": "Шаблоны", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Назначить участника", "attached": "прикреплено", "attachment": "Вложение", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Удалить вложение?", "attachments": "Вложения", "auto-watch": "Автоматически следить за созданными досками", - "avatar-too-big": "Аватар слишком большой (максимум 70КБ)", + "avatar-too-big": "Аватар слишком большой (максимум 520КБ)", "back": "Назад", "board-change-color": "Изменить цвет", "board-nb-stars": "%s избранное", "board-not-found": "Доска не найдена", "board-private-info": "Это доска будет <strong>частной</strong>.", "board-public-info": "Эта доска будет <strong>доступной всем</strong>.", + "board-drag-drop-reorder-or-click-open": "Перетаскивайте, чтобы упорядочить значки доски. Нажмите значок доски чтобы открыть доску.", "boardChangeColorPopup-title": "Изменить фон доски", "boardChangeTitlePopup-title": "Переименовать доску", "boardChangeVisibilityPopup-title": "Изменить настройки видимости", @@ -138,6 +148,7 @@ "board-view-cal": "Календарь", "board-view-swimlanes": "Дорожки", "board-view-collapse": "Свернуть", + "board-view-gantt": "Диаграмма Ганта", "board-view-lists": "Списки", "bucket-example": "Например “Список дел”", "cancel": "Отмена", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Прикрепить из", "cardCustomField-datePopup-title": "Изменить дату", "cardCustomFieldsPopup-title": "редактировать настраиваемые поля", + "cardStartVotingPopup-title": "Голосовать", + "positiveVoteMembersPopup-title": "Сторонники", + "negativeVoteMembersPopup-title": "Противники", + "card-edit-voting": "Редактировать голосование", + "editVoteEndDatePopup-title": "Изменить дату окончания голосования", + "allowNonBoardMembers": "Разрешить всем авторизованным пользователям ", + "vote-question": "Вопрос для голосования", + "vote-public": "Показать кто как голосовал", + "vote-for-it": "за", + "vote-against": "против", + "deleteVotePopup-title": "Удалить голосование?", + "vote-delete-pop": "Это действие невозможно будет отменить. Все связанные с голосованием действия будут потеряны.", + "cardStartPlanningPokerPopup-title": "Начать покер планирования", + "card-edit-planning-poker": "Редактировать покер планирования.", + "editPokerEndDatePopup-title": "Изменить дату окончания голосования покера планирования.", + "poker-question": "Покер планирования", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Закончить", + "poker-result-votes": "Голоса", + "poker-result-who": "Кто", + "poker-replay": "Переиграть", + "set-estimation": "Задать оценку", + "deletePokerPopup-title": "Удалить покер планирования?", + "poker-delete-pop": "Удаление необратимо. Вы потеряете действия ассоциированные с этим покером планирования.", "cardDeletePopup-title": "Удалить карточку?", "cardDetailsActionsPopup-title": "Действия в карточке", "cardLabelsPopup-title": "Метки", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Создать шаблон", "cards": "Карточки", "cards-count": "Карточки", + "cards-count-one": "Карточка", "casSignIn": "Войти через CAS", "cardType-card": "Карточка", "cardType-linkedCard": "Связанная карточка", @@ -191,6 +236,7 @@ "close": "Закрыть", "close-board": "Закрыть доску", "close-board-pop": "Вы сможете восстановить доску, нажав \"Архив\" в заголовке домашней страницы.", + "close-card": "Закрыть карточку", "color-black": "черный", "color-blue": "синий", "color-crimson": "малиновый", @@ -244,6 +290,8 @@ "current": "текущий", "custom-field-delete-pop": "Отменить нельзя. Это удалит настраиваемое поле со всех карт и уничтожит его историю.", "custom-field-checkbox": "Галочка", + "custom-field-currency": "Валюта", + "custom-field-currency-option": "Код валюты", "custom-field-date": "Дата", "custom-field-dropdown": "Выпадающий список", "custom-field-dropdown-none": "(нет)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Вы должны быть участником доски, чтобы сделать это", "error-json-malformed": "Ваше текст не является правильным JSON", "error-json-schema": "Содержимое вашего JSON не содержит информацию в корректном формате", + "error-csv-schema": "Ваши данные CSV (с разделителями запятой)/TSV (с разделителями табулятором) не содержат информации в корректном формате", "error-list-doesNotExist": "Список не найден", "error-user-doesNotExist": "Пользователь не найден", "error-user-notAllowSelf": "Вы не можете пригласить себя", "error-user-notCreated": "Пользователь не создан", "error-username-taken": "Это имя пользователя уже занято", + "error-orgname-taken": "Это название организации уже занято", + "error-teamname-taken": "Это название команды уже занято", "error-email-taken": "Этот адрес уже занят", "export-board": "Экспортировать доску", + "export-board-json": "Экспортировать доску в JSON", + "export-board-csv": "Экспортировать доску в CSV", + "export-board-tsv": "Экспортировать доску в TSV", + "export-board-excel": "Экспортировать доску в Excel", + "user-can-not-export-excel": "Пользователь не может экспортировать в Excel", + "export-board-html": "Экспортировать доску в HTML", + "export-card": "Экспорт карточки", + "export-card-pdf": "Экспорт карточки в PDF", + "user-can-not-export-card-to-pdf": "Пользователь не может экспортировать карточку в PDF", + "exportBoardPopup-title": "Экспортировать доску", + "exportCardPopup-title": "Экспорт карточки", "sort": "Сортировать", "sort-desc": "Нажмите, чтобы отсортировать список", "list-sort-by": "Сортировать список по:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Фильтр", "filter-cards": "Фильтр Карточек или Списков", + "filter-dates-label": "Фильтр по дате", + "filter-no-due-date": "Без срока выполнения", + "filter-overdue": "Просрочено", + "filter-due-today": "Крайний срок - сегодня", + "filter-due-this-week": "Крайний срок - эта неделя", + "filter-due-tomorrow": "Крайний срок - завтра", "list-filter-label": "Фильтровать Список по Названию", "filter-clear": "Очистить фильтр", + "filter-labels-label": "Фильтр по метке", "filter-no-label": "Нет метки", + "filter-member-label": "Фильтр по участнику", "filter-no-member": "Нет участников", + "filter-assignee-label": "Фильтр по исполнителю", + "filter-no-assignee": "Не применимо", + "filter-custom-fields-label": "Фильтр по пользовательскому полю", "filter-no-custom-fields": "Нет настраиваемых полей", "filter-show-archive": "Показать архивные списки", "filter-hide-empty": "Скрыть пустые списки", "filter-on": "Включен фильтр", "filter-on-desc": "Показываются карточки, соответствующие настройкам фильтра. Нажмите для редактирования.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Другие фильтры", "advanced-filter-label": "Расширенный фильтр", "advanced-filter-description": "Расширенный фильтр позволяет написать строку, содержащую следующие операторы: == != <= >= && || ( ) Пробел используется как разделитель между операторами. Можно фильтровать все настраиваемые поля, вводя их имена и значения. Например: Поле1 == Значение1. Примечание. Если поля или значения содержат пробелы, нужно взять их в одинарные кавычки. Например: 'Поле 1' == 'Значение 1'. Для одиночных управляющих символов (' \\/), которые нужно пропустить, следует использовать \\. Например: Field1 = I\\'m. Также можно комбинировать несколько условий. Например: F1 == V1 || F1 == V2. Обычно все операторы интерпретируются слева направо, но можно изменить порядок, разместив скобки. Например: F1 == V1 && (F2 == V2 || F2 == V3). Также можно искать текстовые поля с помощью регулярных выражений: F1 == /Tes.*/i", "fullname": "Полное имя", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Создать доску", "home": "Главная", "import": "Импорт", + "impersonate-user": "Деперсонифицировать пользователя", "link": "Ссылка", "import-board": "импортировать доску", "import-board-c": "Импортировать доску", "import-board-title-trello": "Импортировать доску из Trello", "import-board-title-wekan": "Импортировать доску, сохраненную ранее.", - "import-sandstorm-backup-warning": "Не удаляйте импортируемые данные из ранее сохраненной доски или Trello, пока не убедитесь, что импорт завершился успешно – удается закрыть и снова открыть доску, и не появляется ошибка «Доска не найдена», что означает потерю данных.", - "import-sandstorm-warning": "Импортированная доска удалит все существующие данные на текущей доске и заменит её импортированной доской.", + "import-board-title-csv": "Импортировать доску из CSV/TSV", "from-trello": "Из Trello", "from-wekan": "Сохраненную ранее", + "from-csv": "Из CSV/TSV", "import-board-instruction-trello": "На вашей Trello доске нажмите “Menu” - “More” - “Print and export - “Export JSON” и скопируйте полученный текст", + "import-board-instruction-csv": "Вставка CSV/TSV данных", "import-board-instruction-wekan": "На вашей доске перейдите в “Меню”, далее “Экспортировать доску” и скопируйте текст из скачаного файла", "import-board-instruction-about-errors": "Даже если при импорте возникли ошибки, иногда импортирование проходит успешно – тогда доска появится на странице «Все доски».", "import-json-placeholder": "Вставьте JSON сюда", + "import-csv-placeholder": "Вставьте CSV/TSV сюда", "import-map-members": "Составить карту участников", "import-members-map": "Вы импортировали доску с участниками. Пожалуйста, отметьте участников, которых вы хотите импортировать в качестве пользователей", + "import-members-map-note": "Внимание: участники без установленного соответствия будут назначены текущему пользователю. ", "import-show-user-mapping": "Проверить карту участников", "import-user-select": "Выберите существующего пользователя, которого вы хотите использовать в качестве участника", "importMapMembersAddPopup-title": "Выбрать участника", @@ -375,9 +453,13 @@ "list-select-cards": "Выбрать все карточки в этом списке", "set-color-list": "Задать цвет", "listActionPopup-title": "Список действий", + "settingsUserPopup-title": "Пользовательские настройки", + "settingsTeamPopup-title": "Настройки команды", + "settingsOrgPopup-title": "Настройки организации", "swimlaneActionPopup-title": "Действия с дорожкой", "swimlaneAddPopup-title": "Добавить дорожку ниже", "listImportCardPopup-title": "Импортировать Trello карточку", + "listImportCardsTsvPopup-title": "Импорт CSV/TSV из Excel", "listMorePopup-title": "Поделиться", "link-list": "Ссылка на список", "list-delete-pop": "Все действия будут удалены из ленты активности участников, и вы не сможете восстановить список. Данное действие необратимо.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Переместить вверх", "moveSelectionPopup-title": "Переместить выделение", "multi-selection": "Выбрать несколько", + "multi-selection-label": "Задать метку для отмеченного", + "multi-selection-member": "Задать участника для отмеченного", "multi-selection-on": "Выбрать несколько из", "muted": "Не беспокоить", "muted-info": "Вы НИКОГДА не будете уведомлены ни о каких изменениях в этой доске.", @@ -441,8 +525,9 @@ "search": "Поиск", "rules": "Правила", "search-cards": "Поиск в названиях карточек/списков, описаниях и пользовательских полях на этой доске", - "search-example": "Искать текст?", + "search-example": "Введите текст, который ищете, и нажмите Ввод.", "select-color": "Выбрать цвет", + "select-board": "Выбрать доску", "set-wip-limit-value": "Устанавливает ограничение на максимальное количество задач в этом списке", "setWipLimitPopup-title": "Задать лимит на кол-во задач", "shortcut-assign-self": "Связать себя с текущей карточкой", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Показать мои карточки", "shortcut-show-shortcuts": "Поднять список ярлыков", "shortcut-toggle-filterbar": "Переместить фильтр на бововую панель", + "shortcut-toggle-searchbar": "Включить/выключить боковую панель поиска", "shortcut-toggle-sidebar": "Переместить доску на боковую панель", "show-cards-minimum-count": "Показывать количество карточек если их больше", "sidebar-open": "Открыть Панель", @@ -481,7 +567,15 @@ "upload": "Загрузить", "upload-avatar": "Загрузить аватар", "uploaded-avatar": "Загруженный аватар", + "custom-top-left-corner-logo-image-url": "URL пользовательского изображения в левом верхнем углу", + "custom-top-left-corner-logo-link-url": "URL ссылки с пользовательского изображения в левом верхнем углу", + "custom-top-left-corner-logo-height": "Задать высоту логотипа в верхнем левом углу. По умолчанию 27", + "custom-login-logo-image-url": "URL пользовательского изображения при входе", + "custom-login-logo-link-url": "URL ссылки с пользовательского изображения при входе", + "text-below-custom-login-logo": "Текст под пользовательским изображением при входе", + "automatic-linked-url-schemes": "Пользовательская URL схема, которая должна быть кликабельной. Одна схема в каждой строке.", "username": "Имя пользователя", + "import-usernames": "Импорт имен пользователей", "view-it": "Просмотреть", "warn-list-archived": "внимание: эта карточка из списка, который находится в Архиве", "watch": "Следить", @@ -553,7 +647,8 @@ "minutes": "минуты", "seconds": "секунды", "show-field-on-card": "Показать это поле на карточке", - "automatically-field-on-card": "Cоздавать поле во всех новых карточках", + "automatically-field-on-card": "Добавить поле на новую карточку", + "always-field-on-card": "Добавить поле на все карточки", "showLabel-field-on-card": "Показать имя поля на карточке", "yes": "Да", "no": "Нет", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Разрешить изменение электронной почты", "accounts-allowUserNameChange": "Разрешить изменение имени пользователя", "createdAt": "Создан", + "modifiedAt": "Изменено", "verified": "Подтвержден", "active": "Действующий", "card-received": "Получено", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Выберите цвет", "assigned-by": "Поручил", "requested-by": "Запросил", + "card-sorting-by-number": "Сортировка карточек по номеру", "board-delete-notice": "Удаление является постоянным. Вы потеряете все списки, карты и действия, связанные с этой доской.", "delete-board-confirm-popup": "Все списки, карточки, метки и действия будут удалены, и вы не сможете восстановить содержимое доски. Отменить нельзя.", "boardDeletePopup-title": "Удалить доску?", @@ -614,13 +711,16 @@ "r-delete-rule": "Удалить правило", "r-new-rule-name": "Имя нового правила", "r-no-rules": "Нет правил", + "r-trigger": "Триггер", + "r-action": "Действие", "r-when-a-card": "Когда карточка", "r-is": " ", "r-is-moved": "перемещается", - "r-added-to": "добавляется в", + "r-added-to": "Добавлено в", "r-removed-from": "Покидает", "r-the-board": "доску", "r-list": "список", + "list": "Список", "set-filter": "Установить фильтр", "r-moved-to": "Перемещается в", "r-moved-from": "Покидает", @@ -665,6 +765,7 @@ "r-of-checklist": "контрольного списка", "r-send-email": "Отправить письмо", "r-to": "кому", + "r-of": "из", "r-subject": "тема", "r-rule-details": "Содержание правила", "r-d-move-to-top-gen": "Переместить карточку в начало текущего списка", @@ -725,6 +826,8 @@ "display-authentication-method": "Показывать способ авторизации", "default-authentication-method": "Способ авторизации по умолчанию", "duplicate-board": "Клонировать доску", + "org-number": "Количество организаций:", + "team-number": "Количество команд:", "people-number": "Количество человек:", "swimlaneDeletePopup-title": "Удалить дорожку?", "swimlane-delete-pop": "Все действия будут удалены из ленты активности участников, и вы не сможете восстановить дорожку. Данное действие необратимо.", @@ -750,6 +853,8 @@ "act-duenow": "напомнил, что срок выполнения (__timeValue__) карточки __card__ — это уже сейчас", "act-atUserComment": "Вас упомянули в [__board__] __list__/__card__", "delete-user-confirm-popup": "Вы уверены, что хотите удалить аккаунт? Данное действие необратимо.", + "delete-team-confirm-popup": "Вы уверены, что хотите удалить эту команду? Эту операцию нельзя отменить.", + "delete-org-confirm-popup": "Вы уверены, что хотите удалить эту организацию? Эту операцию нельзя отменить.", "accounts-allowUserDelete": "Разрешить пользователям удалять собственные аккаунты", "hide-minicard-label-text": "Скрыть текст меток на карточках", "show-desktop-drag-handles": "Показать ярлыки для перетаскивания", @@ -758,12 +863,200 @@ "addmore-detail": "Добавить более детальное описание", "show-on-card": "Показать на карточке", "new": "Новый", + "editOrgPopup-title": "Редактировать организацию", + "newOrgPopup-title": "Новая организация", + "editTeamPopup-title": "Редактировать команду", + "newTeamPopup-title": "Новая команда", "editUserPopup-title": "Редактировать пользователя", "newUserPopup-title": "Новый пользователь", "notifications": "Уведомления", "view-all": "Показать все", "filter-by-unread": "Фильтр по непрочитанным", "mark-all-as-read": "Отметить все как прочитанные", + "remove-all-read": "Удалить все прочитанные", "allow-rename": "Разрешить переименование", - "allowRenamePopup-title": "Разрешить переименование" + "allowRenamePopup-title": "Разрешить переименование", + "start-day-of-week": "Установить день начала недели", + "monday": "Понедельник", + "tuesday": "Вторник", + "wednesday": "Среда", + "thursday": "Четверг", + "friday": "Пятница", + "saturday": "Суббота", + "sunday": "Воскресенье", + "status": "Статус", + "swimlane": "Дорожка", + "owner": "Владелец", + "last-modified-at": "Последний раз изменено", + "last-activity": "Последние действия", + "voting": "Голосование", + "archived": "Архивировано", + "delete-linked-card-before-this-card": "Вы не можете удалить карточку, не удалив связанную c ней карточку, которая имеет ", + "delete-linked-cards-before-this-list": "Вы не можете удалить этот список, не удалив карточки, которые указывают на карточки в этом списке", + "hide-checked-items": "Спрятать отмеченные", + "task": "Задача", + "create-task": "Создать задачу", + "ok": "Ok", + "organizations": "Организации", + "teams": "Команды", + "displayName": "Отображаемое название", + "shortName": "Короткое название", + "website": "Вебсайт", + "person": "Представитель", + "my-cards": "Мои карточки", + "card": "Карточка", + "board": "Доска", + "context-separator": "/", + "myCardsSortChange-title": "Сортировать мои карточки", + "myCardsSortChangePopup-title": "Сортировать мои карточки", + "myCardsSortChange-choice-board": "По доскам", + "myCardsSortChange-choice-dueat": "По сроку выполнения", + "dueCards-title": "Карточки с установленным сроком", + "dueCardsViewChange-title": "Просмотр карточки с установленным сроком", + "dueCardsViewChangePopup-title": "Просмотр карточки с установленным сроком", + "dueCardsViewChange-choice-me": "Мне", + "dueCardsViewChange-choice-all": "Все пользователи", + "dueCardsViewChange-choice-all-description": "Показать все незавершенные карточки с установленным сроком с досок для которых у пользователя есть разрешения.", + "broken-cards": "Просроченные карточки", + "board-title-not-found": "Доска '%s' не найдена.", + "swimlane-title-not-found": "Дорожка '%s' не найдена.", + "list-title-not-found": "Список '%s' не найден.", + "label-not-found": "Метка '%1' не найдена.", + "label-color-not-found": "Цвет метки '%1' не найден.", + "user-username-not-found": "Имя пользователя '%s' не найдено.", + "comment-not-found": "Карточка с комментарием, содержащим текст '%s', не найдена.", + "globalSearch-title": "Искать на всех досках", + "no-cards-found": "Ни одной карточки не найдено", + "one-card-found": "Найдена одна карточка", + "n-cards-found": "Карточек найдено %s", + "n-n-of-n-cards-found": "__start__-__end__ из __total__ карточек найдено", + "operator-board": "доска", + "operator-board-abbrev": "д", + "operator-swimlane": "дорожка", + "operator-swimlane-abbrev": "дор.", + "operator-list": "список", + "operator-list-abbrev": "l", + "operator-label": "метку", + "operator-label-abbrev": "#", + "operator-user": "пользователь", + "operator-user-abbrev": "@", + "operator-member": "участника", + "operator-member-abbrev": "м", + "operator-assignee": "Кому назначено", + "operator-assignee-abbrev": "a", + "operator-creator": "автор", + "operator-status": "статус", + "operator-due": "выполнить к", + "operator-created": "создано", + "operator-modified": "изменено", + "operator-sort": "сортировка", + "operator-comment": "комментарий", + "operator-has": "имеет", + "operator-limit": "лимит", + "predicate-archived": "архивировано", + "predicate-open": "открыт", + "predicate-ended": "завершено", + "predicate-all": "все", + "predicate-overdue": "просрочено", + "predicate-week": "неделя", + "predicate-month": "месяц", + "predicate-quarter": "квартал", + "predicate-year": "год", + "predicate-due": "выполнить к", + "predicate-modified": "изменено", + "predicate-created": "создано", + "predicate-attachment": "вложение", + "predicate-description": "описание", + "predicate-checklist": "контрольный список", + "predicate-start": "в работе с", + "predicate-end": "завершено", + "predicate-assignee": "Кому назначено", + "predicate-member": "участника", + "predicate-public": "общедоступно", + "predicate-private": "приватно", + "operator-unknown-error": "%1 не оператор", + "operator-number-expected": "оператор __operator__ используется с числом, задано '__value__'", + "operator-sort-invalid": "сортировка '%s' неверна", + "operator-status-invalid": "'%s' не допустимый статус", + "operator-has-invalid": "%s неверная проверка присутствия", + "operator-limit-invalid": "%s неверное ограничение. Ограничение должно быть положительным целым.", + "next-page": "Следующая страница", + "previous-page": "Предыдущая страница", + "heading-notes": "Заметки", + "globalSearch-instructions-heading": "Инструкция по поиску", + "globalSearch-instructions-description": "Поисковая строка может содержать операторы для уточнения запроса. Оператор задается именем оператора и значением разделенными двоеточием. Например, оператор 'list:Blocked' ограничит результат запроса карточками состоящими в списке 'Blocked'. Если значение содержит пробелы или специальные символы, то оно должно быть заключено в кавычки(__operator_list__:\"To Review\").", + "globalSearch-instructions-operators": "Доступные операторы:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - карточки соответствуют: *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - карточки в списке соответствуют: *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - карточки на дорожках, соответствующих *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - карточки с комментарием содержащим *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - карточки, которые имеют метку соответствующую *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - сокращение для `__operator_label__:<color>` или `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - карточки в которых *<username>* *участник* или *исполнитель*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - сокращение для `пользователь:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - карточки, в которых *<username>* *участник*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - карточки, в которых *<username>* *исполнитель*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - карточки, где *<username>* является автором", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - карточки с датой завершения до *<n>* дней от текущей даты. `__operator_due__:__predicate_overdue__ список всех просроченных карточек.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - карточки, которые были созданы до *<n>* дней назад", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - карточки, который изменены до *<n>* дней назад", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - где *<status>* что-то из следующего:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - архивированные карточки", + "globalSearch-instructions-status-all": "`__predicate_all__` - все архивированные и не архивированные карточки", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - карточки с датой завершения", + "globalSearch-instructions-status-public": "`__predicate_public__` - карточки только на публичных досках", + "globalSearch-instructions-status-private": "`__predicate_private__` - карточки только на личных досках", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - где *<field>* что-либо из `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` или `__predicate_member__`. Указание `-` перед *<field>* производит поиск по отсутствию значения в указанном поле. (напр. `has:-due` поиск карточек без даты завершения).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - где*<sort-name>* что-либо из `__predicate_due__`, `__predicate_created__` или `__predicate_modified__`. Для сортировки по убыванию, укажите `-` перед названием сортировки.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - где *<n>* положительное целое определяющее количество карточек на одной странице.", + "globalSearch-instructions-notes-1": "Может быть задано несколько операторов.", + "globalSearch-instructions-notes-2": "Одинаковые операторы объединяются логическим \"ИЛИ\", например:\n`__operator_list__:Available __operator_list__:Blocked` вернет карточки, которые содержатся в списке *Blocked* или *Available*.", + "globalSearch-instructions-notes-3": "Разные операторы объединяются логическим \"И\". Возвращаются только объекты удовлетворяющие всем заданным условиям.`__operator_list__:Available __operator_label__:red` вернет карточки, которые содержатся в списке *Available* и имеют метку *red*.", + "globalSearch-instructions-notes-3-2": "Количество дней может быть задано положительным или отрицательным целым или используя `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` или `__predicate_year__` для текущего периода.", + "globalSearch-instructions-notes-4": "Текстовый поиск нечувствителен к регистру символов.", + "globalSearch-instructions-notes-5": "По умолчанию, поиск в архивированных карточках не производится.", + "link-to-search": "Ссылка на этот поиск", + "excel-font": "Arial", + "number": "Номер", + "label-colors": "Цвета меток", + "label-names": "Названия меток", + "archived-at": "архивировано", + "sort-cards": "Сортировать карточки", + "cardsSortPopup-title": "Сортировать карточки", + "due-date": "Назначенная дата", + "server-error": "Ошибка сервера", + "server-error-troubleshooting": "Пожалуйста отправьте описание ошибки созданное сервером.\nДля установки с помощью snap, запустите: `sudo snap logs wekan.wekan`\nДля установки с помощью Docker, запустите: `sudo docker logs wekan-app`", + "title-alphabetically": "Название (по алфавиту)", + "created-at-newest-first": "Дата создания (сначала новые)", + "created-at-oldest-first": "Дата создания (сначала старые)", + "links-heading": "Ссылки", + "hide-system-messages-of-all-users": "Скрыть системные сообщения всех пользователей", + "now-system-messages-of-all-users-are-hidden": "Системные сообщения всех пользователей скрыты", + "move-swimlane": "Переместить дорожку", + "moveSwimlanePopup-title": "Переместить дорожку", + "custom-field-stringtemplate": "Строковый шаблон", + "custom-field-stringtemplate-format": "Формат (используйте %{значение} для подстановки)", + "custom-field-stringtemplate-separator": "Разделитель (для пробела используйте или  )", + "custom-field-stringtemplate-item-placeholder": "Нажмите «Ввод», чтобы добавить больше элементов.", + "creator": "Автор", + "filesReportTitle": "Отчёт по файлам", + "orphanedFilesReportTitle": "Отчёт по потерянным файлам", + "reports": "Отчёты", + "rulesReportTitle": "Отчёт по правилам", + "copy-swimlane": "Скопировать дорожку", + "copySwimlanePopup-title": "Скопировать дорожку", + "display-card-creator": "Показать создателя карты", + "wait-spinner": "Спинер ожидания", + "Bounce": "Прыгающий спинер ожидания", + "Cube": "Кубический спинер ожидания", + "Cube-Grid": "Сетка кубиков", + "Dot": "Точки", + "Double-Bounce": "Двойной прыгающий спинер ожидания", + "Rotateplane": "Врщающийся лист", + "Scaleout": "Увеличивающийся-уменьшающийся спиннер", + "Wave": "Волновой спиннер", + "maximize-card": "Максимизировать карточку", + "minimize-card": "Минимизировать карточку", + "delete-org-warning-message": "Невозможно удалить эту организацию, она включает в себя как минимум одного пользователя", + "delete-team-warning-message": "Невозможно удалить эту команду, она включает в себя как минимум одного пользователя" } \ No newline at end of file diff --git a/i18n/sk.i18n.json b/i18n/sk.i18n.json new file mode 100644 index 000000000..660dea5a1 --- /dev/null +++ b/i18n/sk.i18n.json @@ -0,0 +1,1062 @@ +{ + "accept": "Prijať", + "act-activity-notify": "Oznámenie", + "act-addAttachment": "pridal prílohu __attachment__ karte __card__ v stĺpci __list__ vo swimlane __swimlane__ na doske __board__", + "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createBoard": "vytvorená doska __board__", + "act-createSwimlane": "created swimlane __swimlane__ to board __board__", + "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-createCustomField": "created custom field __customField__ at board __board__", + "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-createList": "added list __list__ to board __board__", + "act-addBoardMember": "added member __member__ to board __board__", + "act-archivedBoard": "Doska __board__ presunutá do Archívu", + "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", + "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", + "act-importBoard": "imported board __board__", + "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", + "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", + "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-removeBoardMember": "removed member __member__ from board __board__", + "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", + "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-withBoardTitle": "__board__", + "act-withCardTitle": "[__board__] __card__", + "actions": "Akcie", + "activities": "Aktivity", + "activity": "Aktivita", + "activity-added": "pridal %s do %s", + "activity-archived": "%s presunutá do archívu", + "activity-attached": "priložená %s do %s", + "activity-created": "vytvorená %s", + "activity-customfield-created": "vytvorené vlastné pole %s", + "activity-excluded": "excluded %s from %s", + "activity-imported": "imported %s into %s from %s", + "activity-imported-board": "imported %s from %s", + "activity-joined": "pripojená %s", + "activity-moved": "presunuté %s z %s do %s", + "activity-on": "na %s", + "activity-removed": "odstránené %s z %s", + "activity-sent": "odoslané %s do %s", + "activity-unjoined": "odpojené %s", + "activity-subtask-added": "pridaná podúloha do %s", + "activity-checked-item": "checked %s in checklist %s of %s", + "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-checklist-added": "added checklist to %s", + "activity-checklist-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", + "activity-checklist-item-added": "added checklist item to '%s' in %s", + "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "add": "Pridať", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", + "activity-editComment": "edited comment %s", + "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Pridať prílohu", + "add-board": "Pridať tabuľu", + "add-template": "Add Template", + "add-card": "Pridať kartu", + "add-card-to-top-of-list": "Pridať Kartu na vrch listu", + "add-card-to-bottom-of-list": "Pridať Kartu na spodok listu", + "add-swimlane": "Pridať Swimlane", + "add-subtask": "Pridať Podúlohu", + "add-checklist": "Pridať Checklist", + "add-checklist-item": "Pridať novú položku do Checklistu", + "add-cover": "Pridať obal", + "add-label": "Pridať značku", + "add-list": "Pridať zoznam", + "add-members": "Pridať užívateľa", + "added": "Predaný", + "addMemberPopup-title": "Členovia", + "admin": "Administrátor", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", + "admin-announcement": "Oznámenie", + "admin-announcement-active": "Active System-Wide Announcement", + "admin-announcement-title": "Announcement from Administrator", + "all-boards": "Všetky tabule", + "and-n-other-card": "And __count__ other card", + "and-n-other-card_plural": "And __count__ other cards", + "apply": "Použiť", + "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", + "archive": "Move to Archive", + "archive-all": "Move All to Archive", + "archive-board": "Move Board to Archive", + "archive-card": "Move Card to Archive", + "archive-list": "Move List to Archive", + "archive-swimlane": "Move Swimlane to Archive", + "archive-selection": "Move selection to Archive", + "archiveBoardPopup-title": "Move Board to Archive?", + "archived-items": "Archive", + "archived-boards": "Boards in Archive", + "restore-board": "Obnoviť tabuľu", + "no-archived-boards": "No Boards in Archive.", + "archives": "Archive", + "template": "Template", + "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Assign member", + "attached": "attached", + "attachment": "Príloha", + "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", + "attachmentDeletePopup-title": "Delete Attachment?", + "attachments": "Prílohy", + "auto-watch": "Automatically watch boards when they are created", + "avatar-too-big": "The avatar is too large (520KB max)", + "back": "Späť", + "board-change-color": "Zmeniť farbu", + "board-nb-stars": "%s stars", + "board-not-found": "Board not found", + "board-private-info": "This board will be <strong>private</strong>.", + "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Change Board Background", + "boardChangeTitlePopup-title": "Premenovať tabuľu", + "boardChangeVisibilityPopup-title": "Change Visibility", + "boardChangeWatchPopup-title": "Change Watch", + "boardMenuPopup-title": "Board Settings", + "boardChangeViewPopup-title": "Board View", + "boards": "Tabule", + "board-view": "Board View", + "board-view-cal": "Calendar", + "board-view-swimlanes": "Swimlanes", + "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", + "board-view-lists": "Lists", + "bucket-example": "Like “Bucket List” for example", + "cancel": "Zrušiť", + "card-archived": "This card is moved to Archive.", + "board-archived": "This board is moved to Archive.", + "card-comments-title": "This card has %s comment.", + "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", + "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", + "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-due": "Do", + "card-due-on": "Do", + "card-spent": "Strávený čas", + "card-edit-attachments": "Upraviť prílohy", + "card-edit-custom-fields": "Upraviť vlastné polia", + "card-edit-labels": "Upraviť značky", + "card-edit-members": "Upraviť členov", + "card-labels-title": "Zmeniť značky kartám", + "card-members-title": "Pridať alebo odstrániť členov z dosky", + "card-start": "Štart", + "card-start-on": "Začína", + "cardAttachmentsPopup-title": "Attach From", + "cardCustomField-datePopup-title": "Change date", + "cardCustomFieldsPopup-title": "Upraviť vlastné polia", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Zmazať kartu?", + "cardDetailsActionsPopup-title": "Card Actions", + "cardLabelsPopup-title": "Labels", + "cardMembersPopup-title": "Členovia", + "cardMorePopup-title": "More", + "cardTemplatePopup-title": "Create template", + "cards": "Karty", + "cards-count": "Karty", + "cards-count-one": "Card", + "casSignIn": "Sign In with CAS", + "cardType-card": "Card", + "cardType-linkedCard": "Linked Card", + "cardType-linkedBoard": "Linked Board", + "change": "Zmeniť", + "change-avatar": "Change Avatar", + "change-password": "Zmeniť heslo", + "change-permissions": "Change permissions", + "change-settings": "Change Settings", + "changeAvatarPopup-title": "Change Avatar", + "changeLanguagePopup-title": "Zmeniť jazyk", + "changePasswordPopup-title": "Zmeniť heslo", + "changePermissionsPopup-title": "Change Permissions", + "changeSettingsPopup-title": "Change Settings", + "subtasks": "Subtasks", + "checklists": "Checklists", + "click-to-star": "Click to star this board.", + "click-to-unstar": "Click to unstar this board.", + "clipboard": "Clipboard or drag & drop", + "close": "Close", + "close-board": "Close Board", + "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", + "color-black": "black", + "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", + "color-green": "green", + "color-indigo": "indigo", + "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", + "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", + "color-pink": "pink", + "color-plum": "plum", + "color-purple": "purple", + "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", + "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", + "color-yellow": "yellow", + "unset-color": "Unset", + "comment": "Comment", + "comment-placeholder": "Write Comment", + "comment-only": "Comment only", + "comment-only-desc": "Can comment on cards only.", + "no-comments": "No comments", + "no-comments-desc": "Can not see comments and activities.", + "worker": "Worker", + "worker-desc": "Can only move cards, assign itself to card and comment.", + "computer": "Computer", + "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", + "copy-card-link-to-clipboard": "Copy card link to clipboard", + "linkCardPopup-title": "Link Card", + "searchElementPopup-title": "Search", + "copyCardPopup-title": "Copy Card", + "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", + "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", + "create": "Create", + "createBoardPopup-title": "Create Board", + "chooseBoardSourcePopup-title": "Import board", + "createLabelPopup-title": "Create Label", + "createCustomField": "Create Field", + "createCustomFieldPopup-title": "Create Field", + "current": "current", + "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", + "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Dátum", + "custom-field-dropdown": "Dropdown List", + "custom-field-dropdown-none": "(none)", + "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options-placeholder": "Press enter to add more options", + "custom-field-dropdown-unknown": "(unknown)", + "custom-field-number": "Number", + "custom-field-text": "Text", + "custom-fields": "Custom Fields", + "date": "Dátum", + "decline": "Decline", + "default-avatar": "Default avatar", + "delete": "Zmazať", + "deleteCustomFieldPopup-title": "Delete Custom Field?", + "deleteLabelPopup-title": "Delete Label?", + "description": "Description", + "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", + "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", + "discard": "Discard", + "done": "Done", + "download": "Download", + "edit": "Upraviť", + "edit-avatar": "Change Avatar", + "edit-profile": "Edit Profile", + "edit-wip-limit": "Edit WIP Limit", + "soft-wip-limit": "Soft WIP Limit", + "editCardStartDatePopup-title": "Change start date", + "editCardDueDatePopup-title": "Change due date", + "editCustomFieldPopup-title": "Edit Field", + "editCardSpentTimePopup-title": "Change spent time", + "editLabelPopup-title": "Change Label", + "editNotificationPopup-title": "Edit Notification", + "editProfilePopup-title": "Edit Profile", + "email": "Email", + "email-enrollAccount-subject": "An account created for you on __siteName__", + "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", + "email-fail": "Sending email failed", + "email-fail-text": "Error trying to send email", + "email-invalid": "Invalid email", + "email-invite": "Invite via Email", + "email-invite-subject": "__inviter__ sent you an invitation", + "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", + "email-resetPassword-subject": "Reset your password on __siteName__", + "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", + "email-sent": "Email sent", + "email-verifyEmail-subject": "Verify your email address on __siteName__", + "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", + "enable-wip-limit": "Enable WIP Limit", + "error-board-doesNotExist": "This board does not exist", + "error-board-notAdmin": "You need to be admin of this board to do that", + "error-board-notAMember": "You need to be a member of this board to do that", + "error-json-malformed": "Your text is not valid JSON", + "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", + "error-list-doesNotExist": "This list does not exist", + "error-user-doesNotExist": "This user does not exist", + "error-user-notAllowSelf": "You can not invite yourself", + "error-user-notCreated": "This user is not created", + "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", + "error-email-taken": "Email has already been taken", + "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", + "sort": "Sort", + "sort-desc": "Click to Sort List", + "list-sort-by": "Sort the List By:", + "list-label-modifiedAt": "Last Access Time", + "list-label-title": "Name of the List", + "list-label-sort": "Your Manual Order", + "list-label-short-modifiedAt": "(L)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(M)", + "filter": "Filter", + "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Filter List by Title", + "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", + "filter-no-label": "No label", + "filter-member-label": "Filter by member", + "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", + "filter-no-custom-fields": "No Custom Fields", + "filter-show-archive": "Show archived lists", + "filter-hide-empty": "Hide empty lists", + "filter-on": "Filter is on", + "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", + "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Advanced Filter", + "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", + "fullname": "Celé meno", + "header-logo-title": "Go back to your boards page.", + "hide-system-messages": "Hide system messages", + "headerBarCreateBoardPopup-title": "Create Board", + "home": "Home", + "import": "Import", + "impersonate-user": "Impersonate user", + "link": "Link", + "import-board": "import board", + "import-board-c": "Import board", + "import-board-title-trello": "Import board from Trello", + "import-board-title-wekan": "Import board from previous export", + "import-board-title-csv": "Import board from CSV/TSV", + "from-trello": "Z Trella", + "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", + "import-map-members": "Map members", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", + "import-show-user-mapping": "Review members mapping", + "import-user-select": "Pick your existing user you want to use as this member", + "importMapMembersAddPopup-title": "Select member", + "info": "Version", + "initials": "Initials", + "invalid-date": "Neplatný dátum", + "invalid-time": "Invalid time", + "invalid-user": "Invalid user", + "joined": "joined", + "just-invited": "You are just invited to this board", + "keyboard-shortcuts": "Keyboard shortcuts", + "label-create": "Create Label", + "label-default": "%s label (default)", + "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", + "labels": "Labels", + "language": "Jazyk", + "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "leave-board": "Leave Board", + "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", + "leaveBoardPopup-title": "Leave Board ?", + "link-card": "Link to this card", + "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-move-cards": "Move all cards in this list", + "list-select-cards": "Select all cards in this list", + "set-color-list": "Set Color", + "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Swimlane Actions", + "swimlaneAddPopup-title": "Add a Swimlane below", + "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "More", + "link-list": "Link to this list", + "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", + "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "lists": "Lists", + "swimlanes": "Swimlanes", + "log-out": "Log Out", + "log-in": "Log In", + "loginPopup-title": "Log In", + "memberMenuPopup-title": "Member Settings", + "members": "Členovia", + "menu": "Menu", + "move-selection": "Move selection", + "moveCardPopup-title": "Move Card", + "moveCardToBottom-title": "Move to Bottom", + "moveCardToTop-title": "Move to Top", + "moveSelectionPopup-title": "Move selection", + "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", + "multi-selection-on": "Multi-Selection is on", + "muted": "Muted", + "muted-info": "You will never be notified of any changes in this board", + "my-boards": "My Boards", + "name": "Name", + "no-archived-cards": "No cards in Archive.", + "no-archived-lists": "No lists in Archive.", + "no-archived-swimlanes": "No swimlanes in Archive.", + "no-results": "No results", + "normal": "Normal", + "normal-desc": "Can view and edit cards. Can't change settings.", + "not-accepted-yet": "Invitation not accepted yet", + "notify-participate": "Receive updates to any cards you participate as creater or member", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", + "optional": "optional", + "or": "alebo", + "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", + "page-not-found": "Page not found.", + "password": "Password", + "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", + "participating": "Participating", + "preview": "Preview", + "previewAttachedImagePopup-title": "Preview", + "previewClipboardImagePopup-title": "Preview", + "private": "Private", + "private-desc": "This board is private. Only people added to the board can view and edit it.", + "profile": "Profile", + "public": "Public", + "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", + "quick-access-description": "Star a board to add a shortcut in this bar.", + "remove-cover": "Remove Cover", + "remove-from-board": "Remove from Board", + "remove-label": "Remove Label", + "listDeletePopup-title": "Delete List ?", + "remove-member": "Remove Member", + "remove-member-from-card": "Remove from Card", + "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", + "removeMemberPopup-title": "Remove Member?", + "rename": "Rename", + "rename-board": "Premenovať tabuľu", + "restore": "Restore", + "save": "Uložiť", + "search": "Search", + "rules": "Rules", + "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-example": "Write text you search and press Enter", + "select-color": "Select Color", + "select-board": "Select Board", + "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "setWipLimitPopup-title": "Set WIP Limit", + "shortcut-assign-self": "Assign yourself to current card", + "shortcut-autocomplete-emoji": "Autocomplete emoji", + "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-clear-filters": "Clear all filters", + "shortcut-close-dialog": "Close Dialog", + "shortcut-filter-my-cards": "Filter my cards", + "shortcut-show-shortcuts": "Bring up this shortcuts list", + "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", + "shortcut-toggle-sidebar": "Toggle Board Sidebar", + "show-cards-minimum-count": "Show cards count if list contains more than", + "sidebar-open": "Open Sidebar", + "sidebar-close": "Close Sidebar", + "signupPopup-title": "Create an Account", + "star-board-title": "Click to star this board. It will show up at top of your boards list.", + "starred-boards": "Starred Boards", + "starred-boards-description": "Starred boards show up at the top of your boards list.", + "subscribe": "Subscribe", + "team": "Team", + "this-board": "this board", + "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spent time cards", + "time": "Čas", + "title": "Title", + "tracking": "Tracking", + "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", + "type": "Type", + "unassign-member": "Unassign member", + "unsaved-description": "You have an unsaved description.", + "unwatch": "Unwatch", + "upload": "Upload", + "upload-avatar": "Upload an avatar", + "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", + "username": "Username", + "import-usernames": "Import Usernames", + "view-it": "View it", + "warn-list-archived": "warning: this card is in an list at Archive", + "watch": "Watch", + "watching": "Watching", + "watching-info": "You will be notified of any change in this board", + "welcome-board": "Welcome Board", + "welcome-swimlane": "Milestone 1", + "welcome-list1": "Basics", + "welcome-list2": "Advanced", + "card-templates-swimlane": "Card Templates", + "list-templates-swimlane": "List Templates", + "board-templates-swimlane": "Board Templates", + "what-to-do": "What do you want to do?", + "wipLimitErrorPopup-title": "Invalid WIP Limit", + "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", + "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", + "admin-panel": "Admin Panel", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses": "Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-tls-description": "Enable TLS support for SMTP server", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "smtp-tls": "TLS support", + "send-from": "From", + "send-smtp-test": "Send a test email to yourself", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", + "email-smtp-test-subject": "SMTP Test Email", + "email-smtp-test-text": "You have successfully sent an email", + "error-invitation-code-not-exist": "Invitation code doesn't exist", + "error-notAuthorized": "You are not authorized to view this page.", + "webhook-title": "Webhook Name", + "webhook-token": "Token (Optional for Authentication)", + "outgoing-webhooks": "Outgoing Webhooks", + "bidirectional-webhooks": "Two-Way Webhooks", + "outgoingWebhooksPopup-title": "Outgoing Webhooks", + "boardCardTitlePopup-title": "Card Title Filter", + "disable-webhook": "Disable This Webhook", + "global-webhook": "Global Webhooks", + "new-outgoing-webhook": "New Outgoing Webhook", + "no-name": "(Unknown)", + "Node_version": "Node version", + "Meteor_version": "Meteor version", + "MongoDB_version": "MongoDB version", + "MongoDB_storage_engine": "MongoDB storage engine", + "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "OS_Arch": "OS Arch", + "OS_Cpus": "OS CPU Count", + "OS_Freemem": "OS Free Memory", + "OS_Loadavg": "OS Load Average", + "OS_Platform": "OS Platform", + "OS_Release": "OS Release", + "OS_Totalmem": "OS Total Memory", + "OS_Type": "OS Type", + "OS_Uptime": "OS Uptime", + "days": "days", + "hours": "hours", + "minutes": "minutes", + "seconds": "seconds", + "show-field-on-card": "Show this field on card", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", + "showLabel-field-on-card": "Show field label on minicard", + "yes": "Yes", + "no": "No", + "accounts": "Accounts", + "accounts-allowEmailChange": "Allow Email Change", + "accounts-allowUserNameChange": "Allow Username Change", + "createdAt": "Created at", + "modifiedAt": "Modified at", + "verified": "Verified", + "active": "Active", + "card-received": "Received", + "card-received-on": "Received on", + "card-end": "End", + "card-end-on": "Ends on", + "editCardReceivedDatePopup-title": "Change received date", + "editCardEndDatePopup-title": "Change end date", + "setCardColorPopup-title": "Set color", + "setCardActionsColorPopup-title": "Choose a color", + "setSwimlaneColorPopup-title": "Choose a color", + "setListColorPopup-title": "Choose a color", + "assigned-by": "Assigned By", + "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", + "default-subtasks-board": "Subtasks for __board__ board", + "default": "Default", + "queue": "Queue", + "subtask-settings": "Subtasks Settings", + "card-settings": "Card Settings", + "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", + "boardCardSettingsPopup-title": "Card Settings", + "deposit-subtasks-board": "Deposit subtasks to this board:", + "deposit-subtasks-list": "Landing list for subtasks deposited here:", + "show-parent-in-minicard": "Show parent in minicard:", + "prefix-with-full-path": "Prefix with full path", + "prefix-with-parent": "Prefix with parent", + "subtext-with-full-path": "Subtext with full path", + "subtext-with-parent": "Subtext with parent", + "change-card-parent": "Change card's parent", + "parent-card": "Parent card", + "source-board": "Source board", + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "activity-set-customfield": "set custom field '%s' to '%s' in %s", + "activity-unset-customfield": "unset custom field '%s' in %s", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "New rule title", + "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "When a card", + "r-is": "is", + "r-is-moved": "is moved", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "list": "List", + "set-filter": "Set Filter", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Moved to Archive", + "r-unarchived": "Restored from Archive", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label": "When the label", + "r-list-name": "list name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member", + "r-name": "name", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-archive": "Move to Archive", + "r-unarchive": "Restore from Archive", + "r-card": "card", + "r-add": "Pridať", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-items-check": "items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checklist", + "r-send-email": "Send an email", + "r-to": "to", + "r-of": "of", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Move card to Archive", + "r-d-unarchive": "Restore card from Archive", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-create-card": "Create new card", + "r-in-list": "in list", + "r-in-swimlane": "in swimlane", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all items of a list", + "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist", + "r-by": "by", + "r-add-checklist": "Add checklist", + "r-with-items": "with items", + "r-items-list": "item1,item2,item3", + "r-add-swimlane": "Add swimlane", + "r-swimlane-name": "swimlane name", + "r-board-note": "Note: leave a field empty to match every possible value.", + "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", + "r-when-a-card-is-moved": "When a card is moved to another list", + "r-set": "Set", + "r-update": "Update", + "r-datefield": "date field", + "r-df-start-at": "start", + "r-df-due-at": "due", + "r-df-end-at": "end", + "r-df-received-at": "received", + "r-to-current-datetime": "to current date/time", + "r-remove-value-from": "Remove value from", + "ldap": "LDAP", + "oauth2": "OAuth2", + "cas": "CAS", + "authentication-method": "Authentication method", + "authentication-type": "Authentication type", + "custom-product-name": "Custom Product Name", + "layout": "Layout", + "hide-logo": "Hide Logo", + "add-custom-html-after-body-start": "Add Custom HTML after <body> start", + "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "error-undefined": "Something went wrong", + "error-ldap-login": "An error occurred while trying to login", + "display-authentication-method": "Display Authentication Method", + "default-authentication-method": "Default Authentication Method", + "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "The number of people is:", + "swimlaneDeletePopup-title": "Delete Swimlane ?", + "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "restore-all": "Restore all", + "delete-all": "Delete all", + "loading": "Loading, please wait.", + "previous_as": "last time was", + "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", + "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", + "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", + "a-dueAt": "modified due time to be", + "a-endAt": "modified ending time to be", + "a-startAt": "modified starting time to be", + "a-receivedAt": "modified received time to be", + "almostdue": "current due time %s is approaching", + "pastdue": "current due time %s is past", + "duenow": "current due time %s is today", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", + "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", + "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Allow users to self delete their account", + "hide-minicard-label-text": "Hide minicard label text", + "show-desktop-drag-handles": "Show desktop drag handles", + "assignee": "Assignee", + "cardAssigneesPopup-title": "Assignee", + "addmore-detail": "Add a more detailed description", + "show-on-card": "Show on Card", + "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", + "editUserPopup-title": "Edit User", + "newUserPopup-title": "New User", + "notifications": "Notifications", + "view-all": "View All", + "filter-by-unread": "Filter by Unread", + "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", + "allow-rename": "Allow Rename", + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Usporiadať karty", + "cardsSortPopup-title": "Usporiadať karty", + "due-date": "Do dátumu", + "server-error": "Chyba serveru", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Odkazy", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximalizovať kartu", + "minimize-card": "Minimalizovať kartu", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" +} \ No newline at end of file diff --git a/i18n/sl.i18n.json b/i18n/sl.i18n.json index 30c4123bd..2e0afa106 100644 --- a/i18n/sl.i18n.json +++ b/i18n/sl.i18n.json @@ -64,7 +64,7 @@ "activity-unchecked-item": "odkljukal %s na kontrolnem seznamu %s od %s", "activity-checklist-added": "dodal kontrolni seznam na %s", "activity-checklist-removed": "odstranil kontrolni seznam iz %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "dokončan kontrolni seznam %s od %s", "activity-checklist-uncompleted": "nedokončal kontrolni seznam %s od %s", "activity-checklist-item-added": "dodal postavko kontrolnega seznama na '%s' v %s", "activity-checklist-item-removed": "odstranil postavko kontrolnega seznama iz '%s' v %s", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "nedokončal kontrolni seznam %s", "activity-editComment": "uredil komentar %s", "activity-deleteComment": "izbrisal komentar %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Dodaj priponko", "add-board": "Dodaj tablo", + "add-template": "Add Template", "add-card": "Dodaj kartico", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Dodaj plavalno stezo", "add-subtask": "Dodaj podopravilo", "add-checklist": "Dodaj kontrolni seznam", @@ -113,6 +120,8 @@ "archives": "Arhiv", "template": "Predloga", "templates": "Predloge", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Dodeli člana", "attached": "pripeto", "attachment": "Priponka", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Briši priponko?", "attachments": "Priponke", "auto-watch": "Samodejno spremljaj ustvarjene table", - "avatar-too-big": "Velikost avatarja je prevelika (70kB maks.)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Nazaj", "board-change-color": "Spremeni barvo", "board-nb-stars": "%s zvezdic", "board-not-found": "Tabla ni najdena", "board-private-info": "Ta tabla bo <strong>privatna</strong>.", "board-public-info": "Ta tabla bo <strong>javna</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Spremeni ozadje table", "boardChangeTitlePopup-title": "Preimenuj tablo", "boardChangeVisibilityPopup-title": "Spremeni vidnost", @@ -138,6 +148,7 @@ "board-view-cal": "Koledar", "board-view-swimlanes": "Plavalne steze", "board-view-collapse": "Skrči", + "board-view-gantt": "Gantt", "board-view-lists": "Seznami", "bucket-example": "Kot na primer \"Življenjski seznam\"", "cancel": "Prekliči", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Pripni od", "cardCustomField-datePopup-title": "Spremeni datum", "cardCustomFieldsPopup-title": "Uredi poljubna polja", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Briši kartico?", "cardDetailsActionsPopup-title": "Dejanja kartice", "cardLabelsPopup-title": "Oznake", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Ustvari predlogo", "cards": "Kartice", "cards-count": "Kartic", + "cards-count-one": "Kartica", "casSignIn": "Vpiši se s CAS", "cardType-card": "Kartica", "cardType-linkedCard": "Povezana kartica", @@ -191,6 +236,7 @@ "close": "Zapri", "close-board": "Zapri tablo", "close-board-pop": "Tablo boste lahko obnovili s klikom na gumb »Arhiviraj« na vstopni strani.", + "close-card": "Close Card", "color-black": "črna", "color-blue": "modra", "color-crimson": "temno rdeča", @@ -244,6 +290,8 @@ "current": "trenutno", "custom-field-delete-pop": "Razveljavitve ni. To bo odstranilo to poljubno polje iz vseh kartic in izbrisalo njegovo zgodovino.", "custom-field-checkbox": "Potrditveno polje", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Datum", "custom-field-dropdown": "Spustni seznam", "custom-field-dropdown-none": "(nobeno)", @@ -297,13 +345,27 @@ "error-board-notAMember": "Niste član table.", "error-json-malformed": "Vaše besedilo ni veljaven JSON", "error-json-schema": "Vaši JSON podatki ne vsebujejo pravilnih informacij v ustreznem formatu", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "Seznam ne obstaja", "error-user-doesNotExist": "Uporabnik ne obstaja", "error-user-notAllowSelf": "Ne morete povabiti sebe", "error-user-notCreated": "Ta uporabnik ni ustvarjen", "error-username-taken": "To up. ime že obstaja", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "E-poštni naslov je že zaseden", "export-board": "Izvozi tablo", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Izvozi tablo", + "exportCardPopup-title": "Export card", "sort": "Sortiraj", "sort-desc": "Klikni za sortiranje seznama", "list-sort-by": "Sortiraj po:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(R)", "filter": "Filtriraj", "filter-cards": "Filtriraj kartice ali sezname", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filtriraj seznam po imenu", "filter-clear": "Počisti filter", + "filter-labels-label": "Filter by label", "filter-no-label": "Brez oznake", + "filter-member-label": "Filter by member", "filter-no-member": "Brez člana", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "Brez poljubnih polj", "filter-show-archive": "Prikaži arhivirane sezname", "filter-hide-empty": "Skrij prazne sezname", "filter-on": "Filter vklopljen", "filter-on-desc": "Filtrirane kartice na tej tabli. Kliknite tukaj za urejanje filtra.", "filter-to-selection": "Filtriraj izbrane", + "other-filters-label": "Other Filters", "advanced-filter-label": "Napredni filter", "advanced-filter-description": "Napredni filter omogoča pripravo niza, ki vsebuje naslednje operaterje: == != <= >= && || () Preslednica se uporablja kot ločilo med operatorji. Vsa polja po meri lahko filtrirate tako, da vtipkate njihova imena in vrednosti. Na primer: Polje1 == Vrednost1. Opomba: Če polja ali vrednosti vsebujejo presledke, jih morate postaviti v enojne narekovaje. Primer: 'Polje 1' == 'Vrednost 1'. Če želite preskočiti posamezne kontrolne znake (' \\/), lahko uporabite \\. Na primer: Polje1 == I\\'m. Prav tako lahko kombinirate več pogojev. Na primer: F1 == V1 || F1 == V2. Običajno se vsi operaterji interpretirajo od leve proti desni. Vrstni red lahko spremenite tako, da postavite oklepaje. Na primer: F1 == V1 && ( F2 == V2 || F2 == V3 ). Prav tako lahko po besedilu iščete z uporabo pravil regex: F1 == /Tes.*/i", "fullname": "Polno Ime", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Ustvari tablo", "home": "Domov", "import": "Uvozi", + "impersonate-user": "Impersonate user", "link": "Poveži", "import-board": "uvozi tablo", "import-board-c": "Uvozi tablo", "import-board-title-trello": "Uvozi tablo iz orodja Trello", "import-board-title-wekan": "Uvozi tablo iz prejšnjega izvoza", - "import-sandstorm-backup-warning": "Ne zbrišite podatkov, ki jih uvozite z originalne izvožene table ali Trello, preden preverite ali se tabla uspešno zapre in odpre ali pa boste dobili sporočilo Tabla ni najdena, kar pomeni izgubo podatkov.", - "import-sandstorm-warning": "Uvožena tabla bo izbrisala vse obstoječe podatke na tabli in jih zamenjala z uvoženo tablo.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Iz orodja Trello", "from-wekan": "Od prejšnjega izvoza", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "V vaši Trello tabli pojdite na 'Meni', 'Več', 'Natisni in Izvozi', 'Izvozi JSON', in kopirajte prikazano besedilo.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "V vaši tabli pojdite na 'Meni', 'Izvozi tablo' in kopirajte besedilo iz prenesene datoteke.", "import-board-instruction-about-errors": "Pri napakah med uvozom table v nekaterih primerih uvažanje še deluje, uvožena tabla pa je na strani Vse Table.", "import-json-placeholder": "Tukaj prilepite veljavne JSON podatke", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Mapiraj člane", "import-members-map": "Vaša uvožena tabla vsebuje nekaj članov. Prosimo mapirajte člane, ki jih želite uvoziti, z vašimi uporabniki.", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Preglejte povezane člane", "import-user-select": "Izberite obstoječega uporabnika, ki ga želite uporabiti kot tega člana.", "importMapMembersAddPopup-title": "Izberite člana", @@ -375,9 +453,13 @@ "list-select-cards": "Izberi vse kartice na seznamu", "set-color-list": "Nastavi barvo", "listActionPopup-title": "Dejanja seznama", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Dejanja plavalnih stez", "swimlaneAddPopup-title": "Dodaj plavalno stezo spodaj", "listImportCardPopup-title": "Uvozi Trello kartico", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "Več", "link-list": "Poveži s seznamom", "list-delete-pop": "Vsa dejanja bodo odstranjena iz vira dejavnosti in seznama ne boste mogli obnoviti. Razveljavitve ni.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Premakni na vrh", "moveSelectionPopup-title": "Premakni izbiro", "multi-selection": "Multi-Izbira", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Izbira je omogočena", "muted": "Utišano", "muted-info": "O spremembah na tej tabli ne boste prejemali obvestil.", @@ -441,8 +525,9 @@ "search": "Išči", "rules": "Pravila", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Besedilo za iskanje?", + "search-example": "Write text you search and press Enter", "select-color": "Izberi barvo", + "select-board": "Select Board", "set-wip-limit-value": "Omeji maksimalno število opravil v seznamu", "setWipLimitPopup-title": "Omeji število kartic", "shortcut-assign-self": "Dodeli sebe k trenutni kartici", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtriraj moje kartice", "shortcut-show-shortcuts": "Prikaži seznam bližnjic", "shortcut-toggle-filterbar": "Preklopi stransko vrstico za filter", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Preklopi stransko vrstico table", "show-cards-minimum-count": "Prikaži število kartic, če seznam vsebuje več kot", "sidebar-open": "Odpri stransko vrstico", @@ -481,7 +567,15 @@ "upload": "Naloži", "upload-avatar": "Naloži avatar", "uploaded-avatar": "Naložil avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Up. ime", + "import-usernames": "Import Usernames", "view-it": "Poglej", "warn-list-archived": "opozorilo: ta kartica je v seznamu v arhivu", "watch": "Opazuj", @@ -553,7 +647,8 @@ "minutes": "minute", "seconds": "sekunde", "show-field-on-card": "Prikaži to polje na kartici", - "automatically-field-on-card": "Samodejno dodaj polja na vse kartice", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Prikaži oznako polja na mini kartici", "yes": "Da", "no": "Ne", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Dovoli spremembo e-poštnega naslova", "accounts-allowUserNameChange": "Dovoli spremembo up. imena", "createdAt": "Ustvarjen ob", + "modifiedAt": "Modified at", "verified": "Preverjeno", "active": "Aktivno", "card-received": "Prejeto", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Izberi barvo", "assigned-by": "Dodelil", "requested-by": "Zahteval", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Brisanje je trajno. Izgubili boste vse sezname, kartice in akcije, povezane z desko.", "delete-board-confirm-popup": "Vsi seznami, kartice, oznake in dejavnosti bodo izbrisani in vsebine table ne boste mogli obnoviti. Razveljavitve ni.", "boardDeletePopup-title": "Izbriši tablo?", @@ -614,13 +711,16 @@ "r-delete-rule": "Izbriši pravilo", "r-new-rule-name": "Ime novega pravila", "r-no-rules": "Ni pravil", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "Ko je kartica", "r-is": " ", "r-is-moved": "premaknjena", - "r-added-to": "dodan na", + "r-added-to": "Added to", "r-removed-from": "izbrisan iz", "r-the-board": "tabla", "r-list": "seznam", + "list": "List", "set-filter": "Nastavi filter", "r-moved-to": "premaknjena v", "r-moved-from": "premaknjena iz", @@ -665,6 +765,7 @@ "r-of-checklist": "kontrolnega seznama", "r-send-email": "Pošlji e-pošto", "r-to": "naslovnik", + "r-of": "of", "r-subject": "zadeva", "r-rule-details": "Podrobnosti pravila", "r-d-move-to-top-gen": "Premakni kartico na vrh pripadajočega sezama", @@ -725,6 +826,8 @@ "display-authentication-method": "Prikaži metodo avtentikacije", "default-authentication-method": "Privzeta metoda avtentikacije", "duplicate-board": "Dupliciraj tablo", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "Število ljudi je:", "swimlaneDeletePopup-title": "Zbriši plavalno stezo?", "swimlane-delete-pop": "Vsa dejanja bodo odstranjena iz seznama dejavnosti. Plavalne steze ne boste mogli obnoviti. Razveljavitve ni.", @@ -750,6 +853,8 @@ "act-duenow": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ je sedaj", "act-atUserComment": "Omenjeni ste bili v [__board__] __list__/__card__", "delete-user-confirm-popup": "Ali ste prepričani, da želite izbrisati ta račun? Razveljavitve ni.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Dovoli uporabnikom, da sami izbrišejo svoj račun", "hide-minicard-label-text": "Skrij besedilo oznak na karticah", "show-desktop-drag-handles": "Pokaži ročke za povleko na namizju", @@ -758,12 +863,200 @@ "addmore-detail": "Dodaj podrobnejši opis", "show-on-card": "Prikaži na kartici", "new": "Novo", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Uredi uporabnika", "newUserPopup-title": "Nov uporabnik", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Kartica", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "seznam", + "operator-list-abbrev": "l", + "operator-label": "oznaka", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "član", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "rok", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "rok", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "kontrolni seznam", + "predicate-start": "začetek", + "predicate-end": "konec", + "predicate-assignee": "assignee", + "predicate-member": "član", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Število", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/sr.i18n.json b/i18n/sr.i18n.json index ba6691d82..ab6c0dd30 100644 --- a/i18n/sr.i18n.json +++ b/i18n/sr.i18n.json @@ -64,7 +64,7 @@ "activity-unchecked-item": "unchecked %s in checklist %s of %s", "activity-checklist-added": "lista je dodata u %s", "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", + "activity-checklist-completed": "završena čeklista %s od %s", "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", "activity-checklist-item-added": "added checklist item to '%s' in %s", "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", @@ -75,34 +75,41 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "izmenjen komentar", "activity-deleteComment": "izbrisan komentar", - "add-attachment": "Add Attachment", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", + "add-attachment": "Dodaj prilog", "add-board": "Add Board", - "add-card": "Add Card", + "add-template": "Add Template", + "add-card": "Dodaj karticu", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", + "add-subtask": "Dodaj podzadatak", "add-checklist": "Add Checklist", "add-checklist-item": "Dodaj novu stavku u listu", "add-cover": "Dodaj zaglavlje", - "add-label": "Add Label", + "add-label": "Dodaj oznaku", "add-list": "Dodaj Listu", - "add-members": "Dodaj Članove", + "add-members": "Dodaj članove", "added": "Dodao", "addMemberPopup-title": "Članovi", "admin": "Administrator", "admin-desc": "Može da pregleda i menja kartice, uklanja članove i menja podešavanja table", - "admin-announcement": "Announcement", + "admin-announcement": "Najava", "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", + "admin-announcement-title": "Najava za administratora", "all-boards": "Sve table", "and-n-other-card": "And __count__ other card", "and-n-other-card_plural": "And __count__ other cards", "apply": "Primeni", "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", + "archive": "Premesti u arhivu", + "archive-all": "Premesti sve u arhivu", "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", + "archive-card": "Premesti karticu u arhivu", + "archive-list": "Premesti listu u arhivu", "archive-swimlane": "Move Swimlane to Archive", "archive-selection": "Move selection to Archive", "archiveBoardPopup-title": "Move Board to Archive?", @@ -113,41 +120,45 @@ "archives": "Arhiviraj", "template": "Obrazac", "templates": "Obrasci", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Dodeli člana", "attached": "Prikačeno", "attachment": "Prikačeni dokument", "attachment-delete-pop": "Brisanje prikačenog dokumenta je trajno. Ne postoji vraćanje obrisanog.", - "attachmentDeletePopup-title": "Obrisati prikačeni dokument ?", + "attachmentDeletePopup-title": "Obrisati prikačeni dokument?", "attachments": "Prikačeni dokumenti", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "Avatar je prevelik (maksimum je 70KB)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Nazad", "board-change-color": "Promeni boju", "board-nb-stars": "%s zvezdice", "board-not-found": "Tabla nije pronađena", "board-private-info": "Ova tabla će biti <strong>privatna<strong>.", "board-public-info": "Ova tabla će biti <strong>javna<strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Promeni pozadinu table", "boardChangeTitlePopup-title": "Preimenuj tablu", - "boardChangeVisibilityPopup-title": "Promeni Vidljivost", + "boardChangeVisibilityPopup-title": "Promeni vidljivost", "boardChangeWatchPopup-title": "Change Watch", "boardMenuPopup-title": "Board Settings", "boardChangeViewPopup-title": "Board View", "boards": "Table", "board-view": "Board View", - "board-view-cal": "Calendar", + "board-view-cal": "Kalendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Sažmi", - "board-view-lists": "Lists", + "board-view-gantt": "Gantt", + "board-view-lists": "Liste", "bucket-example": "Na primer \"Lista zadataka\"", "cancel": "Otkaži", - "card-archived": "This card is moved to Archive.", + "card-archived": "Ova kartica je premeštena u arhivu.", "board-archived": "This board is moved to Archive.", "card-comments-title": "Ova kartica ima %s komentar.", "card-delete-notice": "Brisanje je trajno. Izgubićeš sve akcije povezane sa ovom karticom.", "card-delete-pop": "Sve akcije će biti uklonjene sa liste aktivnosti i kartica neće moći biti ponovo otvorena. Nema vraćanja unazad.", "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "Krajnji datum", + "card-due": "Rok", "card-due-on": "Završava se", "card-spent": "Spent Time", "card-edit-attachments": "Uredi priloge", @@ -159,128 +170,165 @@ "card-start": "Početak", "card-start-on": "Počinje", "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", + "cardCustomField-datePopup-title": "Promeni datum", "cardCustomFieldsPopup-title": "Edit custom fields", - "cardDeletePopup-title": "Delete Card?", + "cardStartVotingPopup-title": "Novo glasanje", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Pitanje za glasanje", + "vote-public": "Show who voted what", + "vote-for-it": "za", + "vote-against": "protiv", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Obrisati karticu?", "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", + "cardLabelsPopup-title": "Oznake", "cardMembersPopup-title": "Članovi", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", + "cardMorePopup-title": "Više", + "cardTemplatePopup-title": "Napravi šemu", + "cards": "Kartice", + "cards-count": "Kartice", + "cards-count-one": "Kartica", "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", + "cardType-card": "Kartica", + "cardType-linkedCard": "Povezana kartica", "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Izmeni podešavanja", - "changeAvatarPopup-title": "Change Avatar", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Izmeni podešavanja", - "subtasks": "Subtasks", + "change": "Promeni", + "change-avatar": "Promeni avatara", + "change-password": "Promeni lozinku", + "change-permissions": "Promeni dozvole", + "change-settings": "Promeni podešavanja", + "changeAvatarPopup-title": "Promeni avatara", + "changeLanguagePopup-title": "Promeni jezik", + "changePasswordPopup-title": "Promeni lozinku", + "changePermissionsPopup-title": "Promeni dozvole", + "changeSettingsPopup-title": "Promeni podešavanja", + "subtasks": "Podzadaci", "checklists": "Liste", "click-to-star": "Click to star this board.", "click-to-unstar": "Click to unstar this board.", "clipboard": "Clipboard or drag & drop", - "close": "Close", + "close": "Zatvori", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", + "close-card": "Close Card", + "color-black": "crno", + "color-blue": "plavo", + "color-crimson": "grimizno", + "color-darkgreen": "tamnozeleno", + "color-gold": "zlatno", + "color-gray": "sivo", + "color-green": "zeleno", "color-indigo": "indigo", "color-lime": "lime", "color-magenta": "magenta", "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", + "color-navy": "mornarsko", + "color-orange": "narandžasto", "color-paleturquoise": "paleturquoise", "color-peachpuff": "peachpuff", "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", + "color-plum": "šljiva", + "color-purple": "ljubičasto", + "color-red": "crveno", "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", + "color-silver": "srebrno", + "color-sky": "nebesko", "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", + "color-white": "belo", + "color-yellow": "žuto", "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", + "comment": "Komentar", + "comment-placeholder": "Napiši komentar", + "comment-only": "Samo komentari", "comment-only-desc": "Can comment on cards only.", "no-comments": "No comments", "no-comments-desc": "Can not see comments and activities.", "worker": "Radnik", "worker-desc": "Može samo da pomera kartice, dodeljuje sebe kartici i da komentariše. ", - "computer": "Computer", + "computer": "Računar", "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", + "linkCardPopup-title": "Poveži karticu", "searchElementPopup-title": "Pretraga", - "copyCardPopup-title": "Copy Card", + "copyCardPopup-title": "Kopiraj karticu", "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", + "create": "Napravi", "createBoardPopup-title": "Create Board", "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", + "createLabelPopup-title": "Napravi oznaku", + "createCustomField": "Napravi polje", + "createCustomFieldPopup-title": "Napravi polje", "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Datum", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", + "custom-field-dropdown": "Padajuća lista", + "custom-field-dropdown-none": "(ništa)", + "custom-field-dropdown-options": "Opcije liste", "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", + "custom-field-dropdown-unknown": "(nepoznato)", + "custom-field-number": "Broj", + "custom-field-text": "Tekst", "custom-fields": "Custom Fields", "date": "Datum", - "decline": "Decline", + "decline": "Odbij", "default-avatar": "Default avatar", - "delete": "Delete", + "delete": "Obriši", "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", + "deleteLabelPopup-title": "Obrisati oznaku?", + "description": "Opis", "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", + "discard": "Odbaci", + "done": "Završeno", + "download": "Preuzmi", + "edit": "Uredi", + "edit-avatar": "Promeni avatara", + "edit-profile": "Uredi profil", "edit-wip-limit": "Edit WIP Limit", "soft-wip-limit": "Soft WIP Limit", "editCardStartDatePopup-title": "Izmeni početni datum", "editCardDueDatePopup-title": "Izmeni krajnji datum", - "editCustomFieldPopup-title": "Edit Field", + "editCustomFieldPopup-title": "Izmeni polje", "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Izmeni notifikaciju", - "editProfilePopup-title": "Edit Profile", - "email": "Email", + "editLabelPopup-title": "Izmeni oznaku", + "editNotificationPopup-title": "Izmeni obaveštenje", + "editProfilePopup-title": "Izmeni profil", + "email": "Epošta", "email-enrollAccount-subject": "An account created for you on __siteName__", "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", + "email-fail": "Neuspelo slanje epošte", "email-fail-text": "Error trying to send email", "email-invalid": "Invalid email", "email-invite": "Invite via Email", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", + "error-user-doesNotExist": "Korisnik ne postoji", + "error-user-notAllowSelf": "Ne možeš pozvati samog sebe", + "error-user-notCreated": "Korisnik nije kreiran", "error-username-taken": "Korisničko ime je već zauzeto", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sortiraj", "sort-desc": "Kliknite da biste sortirali listu", "list-sort-by": "Poredaj listu po:", @@ -315,87 +377,109 @@ "list-label-short-sort": "(R)", "filter": "Filter", "filter-cards": "Filtriraj kartice ili liste", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filtriraj liste po naslovu", - "filter-clear": "Clear filter", + "filter-clear": "Očisti filter", + "filter-labels-label": "Filter by label", "filter-no-label": "Nema oznake", + "filter-member-label": "Filter by member", "filter-no-member": "Nema člana", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "Nema zastupnika", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Prikaži arhivirane liste", "filter-hide-empty": "Sakrij prazne liste", - "filter-on": "Filter is on", + "filter-on": "Filter je uključen", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", + "other-filters-label": "Other Filters", + "advanced-filter-label": "Napredni filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", + "fullname": "Puno ime", "header-logo-title": "Go back to your boards page.", "hide-system-messages": "Sakrij sistemske poruke", "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "link": "Link", + "home": "Početna", + "import": "Uvezi", + "impersonate-user": "Impersonate user", + "link": "Veza", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Uvezi tablu iz Trella", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text", + "from-csv": "From CSV/TSV", + "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Mapiraj članove", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", + "importMapMembersAddPopup-title": "Izaberi člana", + "info": "Verzija", + "initials": "Inicijali", "invalid-date": "Neispravan datum", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", + "invalid-time": "Neispravno vreme", + "invalid-user": "Neispravan korisnik", "joined": "joined", "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", + "keyboard-shortcuts": "Prečice tastature", + "label-create": "Napravi oznaku", "label-default": "%s label (default)", "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", + "labels": "Oznake", + "language": "Jezik", "last-admin-desc": "You can’t change roles because there must be at least one admin.", "leave-board": "Leave Board", "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", + "link-card": "Veza do ove kartice", "list-archive-cards": "Move all cards in this list to Archive", "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", + "list-move-cards": "Premesti sve kartice u ovoj listi", + "list-select-cards": "Izaberi sve kartice u ovoj listi", + "set-color-list": "Postavi boju", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", - "listMorePopup-title": "More", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", + "listMorePopup-title": "Više", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", + "lists": "Liste", "swimlanes": "Swimlanes", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", + "log-out": "Odjavi se", + "log-in": "Prijava", + "loginPopup-title": "Prijava", "memberMenuPopup-title": "Member Settings", "members": "Članovi", - "menu": "Menu", + "menu": "Meni", "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", + "moveCardPopup-title": "Premesti karticu", "moveCardToBottom-title": "Premesti na dno", "moveCardToTop-title": "Premesti na vrh", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Utišano", "muted-info": "Nećete biti obavešteni o promenama u ovoj tabli", @@ -409,7 +493,7 @@ "normal-desc": "Can view and edit cards. Can't change settings.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creater or member", - "notify-watch": "Budite obavešteni o novim događajima u tablama, listama ili karticama koje pratite.", + "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", "optional": "opciono", "or": "ili", "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", @@ -428,21 +512,22 @@ "quick-access-description": "Star a board to add a shortcut in this bar.", "remove-cover": "Remove Cover", "remove-from-board": "Ukloni iz table", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", + "remove-label": "Ukloni oznaku", + "listDeletePopup-title": "Obrisati listu?", "remove-member": "Ukloni člana", "remove-member-from-card": "Ukloni iz kartice", "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Ukloni člana ?", + "removeMemberPopup-title": "Ukloni člana?", "rename": "Preimenuj", "rename-board": "Preimenuj tablu", "restore": "Oporavi", "save": "Snimi", "search": "Pretraga", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", + "rules": "Pravila", + "search-cards": "Pretraži u naslovima kartica/listi, opsima i proizvoljnim poljima sa ove table", + "search-example": "Write text you search and press Enter", + "select-color": "Izaberi boju", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Pridruži sebe trenutnoj kartici", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filtriraj kartice", "shortcut-show-shortcuts": "Prikaži ovu listu prečica", "shortcut-toggle-filterbar": "Uključi ili isključi bočni meni filtera", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Uključi ili isključi bočni meni table", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -474,14 +560,22 @@ "title": "Naslov", "tracking": "Praćenje", "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", + "type": "Tip", "unassign-member": "Unassign member", "unsaved-description": "Imaš nesnimljen opis.", "unwatch": "Ne posmatraj", "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Korisničko ime", + "import-usernames": "Import Usernames", "view-it": "Pregledaj je", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Posmatraj", @@ -491,22 +585,22 @@ "welcome-swimlane": "Milestone 1", "welcome-list1": "Osnove", "welcome-list2": "Napredno", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", + "card-templates-swimlane": "Šeme kartice", + "list-templates-swimlane": "Šeme liste", "board-templates-swimlane": "Board Templates", "what-to-do": "Šta želiš da uradiš ?", "wipLimitErrorPopup-title": "Invalid WIP Limit", "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", + "settings": "Podešavanja", + "people": "Ljudi", + "registration": "Registracija", "disable-self-registration": "Disable Self-Registration", "invite": "Invite", "invite-people": "Invite People", "to-boards": "To board(s)", - "email-addresses": "Email Addresses", + "email-addresses": "Adrese epošte", "smtp-host-description": "The address of the SMTP server that handles your emails.", "smtp-port-description": "The port your SMTP server uses for outgoing emails.", "smtp-tls-description": "Enable TLS support for SMTP server", @@ -514,8 +608,8 @@ "smtp-port": "SMTP Port", "smtp-username": "Korisničko ime", "smtp-password": "Lozinka", - "smtp-tls": "TLS support", - "send-from": "From", + "smtp-tls": "TLS podrška", + "send-from": "Od", "send-smtp-test": "Send a test email to yourself", "invitation-code": "Invitation Code", "email-invite-register-subject": "__inviter__ sent you an invitation", @@ -533,8 +627,8 @@ "disable-webhook": "Onesposobi ovu mrežnu kuku", "global-webhook": "Globalna mrežna kuka", "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", + "no-name": "(Nepoznato)", + "Node_version": "Verzija čvora", "Meteor_version": "Verzija Meteor-a", "MongoDB_version": "Verzija MongoDB-a", "MongoDB_storage_engine": "MongoDB mehanizam za skladištenje", @@ -548,19 +642,21 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", + "days": "dana", + "hours": "sati", + "minutes": "minuta", + "seconds": "sekundi", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", + "yes": "Da", + "no": "Ne", + "accounts": "Nalozi", "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -569,18 +665,19 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", + "setCardColorPopup-title": "Podesi boju", + "setCardActionsColorPopup-title": "Izaberi boju", + "setSwimlaneColorPopup-title": "Izaberi boju", + "setListColorPopup-title": "Izaberi boju", "assigned-by": "Dodeljeno od strane", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", + "default": "Predefinisano", "queue": "Queue", "subtask-settings": "Subtasks Settings", "card-settings": "Podešavanja kartice", @@ -605,22 +702,25 @@ "activity-delete-attach-card": "deleted an attachment", "activity-set-customfield": "set custom field '%s' to '%s' in %s", "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", + "r-rule": "Pravilo", + "r-add-trigger": "Dodaj okidač", + "r-add-action": "Dodaj akciju", "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", + "r-add-rule": "Dodaj pravilo", + "r-view-rule": "Pregledaj pravilo", + "r-delete-rule": "Obriši pravilo", + "r-new-rule-name": "Novi naslov pravila", + "r-no-rules": "Nema pravila", + "r-trigger": "Trigger", + "r-action": "Action", + "r-when-a-card": "Kada kartica", + "r-is": "je", + "r-is-moved": "je premeštena", + "r-added-to": "Added to", + "r-removed-from": "Uklonjena iz", + "r-the-board": "table", + "r-list": "liste", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -636,7 +736,7 @@ "r-when-a-attach": "When an attachment", "r-when-a-checklist": "When a checklist is", "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", + "r-completed": "Završeno", "r-made-incomplete": "Made incomplete", "r-when-a-item": "When a checklist item is", "r-when-the-item": "When the checklist item", @@ -648,11 +748,11 @@ "r-its-list": "its list", "r-archive": "Move to Archive", "r-unarchive": "Restore from Archive", - "r-card": "card", + "r-card": "kartica", "r-add": "Dodaj", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", + "r-remove": "Ukloni", + "r-label": "oznaka", + "r-member": "član", "r-remove-all": "Remove all members from the card", "r-set-color": "Set color to", "r-checklist": "checklist", @@ -661,30 +761,31 @@ "r-items-check": "items of checklist", "r-check": "Check", "r-uncheck": "Uncheck", - "r-item": "item", + "r-item": "stavka", "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", - "r-rule-details": "Rule details", + "r-send-email": "Pošalji epoštu", + "r-to": "za", + "r-of": "of", + "r-subject": "naslov", + "r-rule-details": "Detalji pravila", "r-d-move-to-top-gen": "Move card to top of its list", "r-d-move-to-top-spec": "Move card to top of list", "r-d-move-to-bottom-gen": "Move card to bottom of its list", "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", + "r-d-send-email": "Pošalji epoštu", + "r-d-send-email-to": "za", + "r-d-send-email-subject": "naslov", + "r-d-send-email-message": "poruka", "r-d-archive": "Move card to Archive", "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", + "r-d-add-label": "Dodaj oznaku", + "r-d-remove-label": "Ukloni oznaku", + "r-create-card": "Napravi novu karticu", + "r-in-list": "u listi", "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", + "r-d-add-member": "Dodaj člana", + "r-d-remove-member": "Ukloni člana", + "r-d-remove-all-member": "Ukloni sve članove", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", "r-d-check-one": "Check item", @@ -713,11 +814,11 @@ "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", + "authentication-method": "Metod autentifikacije", + "authentication-type": "Tip autentifikacije", "custom-product-name": "Custom Product Name", - "layout": "Layout", - "hide-logo": "Hide Logo", + "layout": "Raspored", + "hide-logo": "Sakrij logo", "add-custom-html-after-body-start": "Add Custom HTML after <body> start", "add-custom-html-before-body-end": "Add Custom HTML before </body> end", "error-undefined": "Something went wrong", @@ -725,12 +826,14 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", + "delete-all": "Obriši sve", + "loading": "Učitavanje, molim sačekajte.", "previous_as": "prošli put je bio", "act-a-dueAt": "modifikovano u vreme\nKada:__vremenskaVrednost__\nGde:__kartica__\nprethodni rok je bio__StaraVremenskaVrednost", "act-a-endAt": "izmenjeno vreme završetaka za__vremenskaVrednost__od (__StaraVremenskaVrednost__)", @@ -750,20 +853,210 @@ "act-duenow": "podsećao da je trenutni krajnji rok (__vremenskaVrednost__) __kartica__ trenutno", "act-atUserComment": "Spomenuti ste na [__tabla__] __lista__/__kartica__", "delete-user-confirm-popup": "Da li ste sigurni da želite da izbrišete nalog? Nema poništenja akcije. ", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Dozvoli korisnicima da sami brišu svoj nalog", "hide-minicard-label-text": "Sakrij tekst nalepnice minikartice", "show-desktop-drag-handles": "Prikaži kvake za povlačenje sa radne površine", - "assignee": "Asignat", - "cardAssigneesPopup-title": "Asignat", + "assignee": "Zastupnik", + "cardAssigneesPopup-title": "Zastupnik", "addmore-detail": "Dodaj detaljaniji opis", "show-on-card": "Prikaži na kartici", "new": "Novo", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Uredi korisnika", "newUserPopup-title": "Novi korisnik", "notifications": "Obaveštenja", "view-all": "Prikaži sve", "filter-by-unread": "Filtriraj nepročitano", "mark-all-as-read": "Označi sve kao pročitano", + "remove-all-read": "Ukloni sve pročitano", "allow-rename": "Dozvoli preimenovanje", - "allowRenamePopup-title": "Dozvoli preimenovanje" + "allowRenamePopup-title": "Dozvoli preimenovanje", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Kartica", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "liste", + "operator-list-abbrev": "l", + "operator-label": "oznaka", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "član", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "krajnji datum", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "krajnji datum", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "početak", + "predicate-end": "kraj", + "predicate-assignee": "assignee", + "predicate-member": "član", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Broj", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/sv.i18n.json b/i18n/sv.i18n.json index 54844f7ed..a2ff0db42 100644 --- a/i18n/sv.i18n.json +++ b/i18n/sv.i18n.json @@ -1,87 +1,94 @@ { "accept": "Acceptera", - "act-activity-notify": "Aktivitetsnotifiering", - "act-addAttachment": "lade till bifogad fil __attachment__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-deleteAttachment": "raderade bifogad fil __attachment__ från kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addSubtask": "lade till underaktivitet __subtask__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addLabel": "lade till etikett __label__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addedLabel": "lade till etikett __label__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-removeLabel": "Tog bort etikett __label__ från kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-removedLabel": "Tog bort etikett __label__ från kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addChecklist": "lade till checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addChecklistItem": "lade till checklistobjekt __checklistItem__ till checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-removeChecklist": "tag bort checklista __checklist__ från kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-removeChecklistItem": "tog bort checklistobjekt __checklistItem__ från __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-checkedItem": "bockade av __checklistItem__ från checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-uncheckedItem": "avmarkerade __checklistItem__ från checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-completeChecklist": "slutförde checklista __checklist__ i kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-uncompleteChecklist": "ofullbordade checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-addComment": "kommenterade på kort __card__: __comment__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-editComment": "redigerade kommentar på kort __card__: __comment__ i listan __list__ i simbana __swimlane__ i brädet __board__", - "act-deleteComment": "raderade kommentar på kort __card__: __comment__ i listan __list__ i simbana __swimlane__ i brädet __board__", - "act-createBoard": "skapade anslagstavla __board__", - "act-createSwimlane": "skapade simbana __swimlane__ till anslagstavla __board__", - "act-createCard": "skapade kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-createCustomField": "skapade anpassat fält __customField__ på anslagstavlan __board__", - "act-deleteCustomField": "raderade anpasssat fält __customField__ på bräde __board__", - "act-setCustomField": "redigerade anpassat fält __customField__: __customFieldValue__ på kort __card__ i lista __list__ i simbana __swimlane__ på bräde __board__", - "act-createList": "lade till lista __list__ på anslagstavla __board__", - "act-addBoardMember": "lade till medlem __member__ på anslagstavla __board__", - "act-archivedBoard": "Anslagstavla __board__ flyttad till arkivet", - "act-archivedCard": "Kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__ flyttad till arkivet", - "act-archivedList": "Lista __list__ i simbana __swimlane__ på anslagstavla __board__ flyttad till arkivet", - "act-archivedSwimlane": "Simbana __swimlane__ på anslagstavla __board__ flyttad till arkivet", - "act-importBoard": "importerade board __board__", - "act-importCard": "importerade kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-importList": "importerade lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-joinMember": "lade till medlem __member__ på kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-moveCard": "flyttade kort __card__ på anslagstavla __board__ från lista __oldList__ i sambana __oldSwimlane__ till lista list __list__ i simbana __swimlane__", - "act-moveCardToOtherBoard": "flyttade kort __card__ från lista __oldList__ i simbana __oldSwimlane__ på tavla __oldBoard__ till lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "act-removeBoardMember": "borttagen medlem __member__  från anslagstavla __board__", - "act-restoredCard": "återställde kort __card__ till lista __lis__ i simbana __swimlane__ på anslagstavla __board__", - "act-unjoinMember": "tog bort medlem __member__ från kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", + "act-activity-notify": "Aktivitetsnotis", + "act-addAttachment": "lade till filen __attachment__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-deleteAttachment": "raderade filen __attachment__ från kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addSubtask": "lade till underaktivitet __subtask__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addLabel": "lade till etikett __label__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addedLabel": "lade till etikett __label__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-removeLabel": "Tog bort etikett __label__ från kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-removedLabel": "Tog bort etikett __label__ från kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addChecklist": "lade till checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addChecklistItem": "lade till checklisteobjekt __checklistItem__ till checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-removeChecklist": "tog bort checklista __checklist__ från kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-removeChecklistItem": "tog bort checklisteobjekt __checklistItem__ från __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-checkedItem": "klarmarkerade __checklistItem__ från checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-uncheckedItem": "avmarkerade __checklistItem__ från checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-completeChecklist": "slutförde checklista __checklist__ i kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-uncompleteChecklist": "ofullbordade checklista __checklist__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-addComment": "kommenterade på kort __card__: __comment__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-editComment": "redigerade kommentar på kort __card__: __comment__ i listan __list__ i simbana __swimlane__ på tavla __board__", + "act-deleteComment": "raderade kommentar på kort __card__: __comment__ i listan __list__ i simbana __swimlane__ på tavla __board__", + "act-createBoard": "skapade tavla __board__", + "act-createSwimlane": "skapade simbana __swimlane__ i tavla __board__", + "act-createCard": "skapade kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-createCustomField": "skapade anpassat fält __customField__ på tavla __board__", + "act-deleteCustomField": "raderade anpassat fält __customField__ på tavla __board__", + "act-setCustomField": "redigerade anpassat fält __customField__: __customFieldValue__ på kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-createList": "lade till lista __list__ på tavla __board__", + "act-addBoardMember": "lade till medlem __member__ på tavla __board__", + "act-archivedBoard": "Tavlan __board__ flyttad till arkivet", + "act-archivedCard": "Kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__ flyttad till Arkiv", + "act-archivedList": "Lista __list__ i simbana __swimlane__ på tavla __board__ flyttad till Arkiv", + "act-archivedSwimlane": "Simbana __swimlane__ på tavla __board__ flyttad till Arkiv", + "act-importBoard": "importerade tavla __board__", + "act-importCard": "importerade kort __card__ till lista __list__ i simbana __swimlane__ på tavla __board__", + "act-importList": "importerade lista __list__ till simbana __swimlane__ på tavla __board__", + "act-joinMember": "lade till medlem __member__ till kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-moveCard": "flyttade kort __card__ på tavla __board__ från lista __oldList__ i simbana __oldSwimlane__ till lista __list__ i simbana __swimlane__", + "act-moveCardToOtherBoard": "flyttade kort __card__ från lista __oldList__ i simbana __oldSwimlane__ på tavla __oldBoard__ till lista __list__ i simbana __swimlane__ på tavla __board__", + "act-removeBoardMember": "tog bort medlem __member__ från tavla __board__", + "act-restoredCard": "återställde kort __card__ till lista __list__ i simbana __swimlane__ på tavla __board__", + "act-unjoinMember": "tog bort medlem __member__ från kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", - "actions": "Åtgärder", + "actions": "Händelser", "activities": "Aktiviteter", "activity": "Aktivitet", - "activity-added": "Lade %s till %s", + "activity-added": "lade till %s till %s", "activity-archived": "%s flyttades till Arkiv", - "activity-attached": "bifogade %s to %s", + "activity-attached": "bifogade %s till %s", "activity-created": "skapade %s", - "activity-customfield-created": "skapa anpassat fält %s", + "activity-customfield-created": "skapade anpassat fält %s", "activity-excluded": "exkluderade %s från %s", "activity-imported": "importerade %s till %s från %s", "activity-imported-board": "importerade %s från %s", - "activity-joined": "anslöt sig till %s", - "activity-moved": "tog bort %s från %s till %s", + "activity-joined": "gick med i %s", + "activity-moved": "flyttade %s från %s till %s", "activity-on": "på %s", "activity-removed": "tog bort %s från %s", "activity-sent": "skickade %s till %s", - "activity-unjoined": "gick ur %s", + "activity-unjoined": "lämnade %s", "activity-subtask-added": "lade till deluppgift till %s", - "activity-checked-item": "kryssad %s i checklistan %s av %s", - "activity-unchecked-item": "okryssad %s i checklistan %s av %s", - "activity-checklist-added": "lade kontrollista till %s", + "activity-checked-item": "klarmarkerade %s i checklistan %s av %s", + "activity-unchecked-item": "avmarkerade %s i checklistan %s av %s", + "activity-checklist-added": "lade till checklista till %s", "activity-checklist-removed": "tog bort en checklista från %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "inte slutfört checklistan %s av %s", - "activity-checklist-item-added": "lade checklista objekt till '%s' i %s", - "activity-checklist-item-removed": "tog bort en checklista objekt från \"%s\" i %s", + "activity-checklist-completed": "slutförde checklista %s av %s", + "activity-checklist-uncompleted": "ofullbordade checklistan %s av %s", + "activity-checklist-item-added": "lade till checklistobjekt till \"%s\" i %s", + "activity-checklist-item-removed": "tog bort ett checklistobjekt från \"%s\" i %s", "add": "Lägg till", - "activity-checked-item-card": "kryssad %s i checklistan %s", - "activity-unchecked-item-card": "okryssad %s i checklistan %s", - "activity-checklist-completed-card": "slutförde checklista __checklist__ i kort __card__ i lista __list__ i simbana __swimlane__ på anslagstavla __board__", - "activity-checklist-uncompleted-card": "icke slutfört checklistan %s", - "activity-editComment": "redigerade kommentaren %s", - "activity-deleteComment": "tog bort kommentaren %s", + "activity-checked-item-card": "klarmarkerade %s i checklistan %s", + "activity-unchecked-item-card": "avmarkerade %s i checklistan %s", + "activity-checklist-completed-card": "slutförde checklista __checklist__ i kort __card__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "activity-checklist-uncompleted-card": "ofullbordade checklistan %s", + "activity-editComment": "redigerade kommentar %s", + "activity-deleteComment": "raderade kommentar %s", + "activity-receivedDate": "redigerade mottaget datum till %s av %s", + "activity-startDate": "redigerade startdatum till %s av %s", + "activity-dueDate": "redigerade förfallodag till %s av %s", + "activity-endDate": "redigerade slutdatum till %s av %s", "add-attachment": "Lägg till bilaga", - "add-board": "Lägg till anslagstavla", + "add-board": "Lägg till tavla", + "add-template": "Add Template", "add-card": "Lägg till kort", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Lägg till simbana", "add-subtask": "Lägg till deluppgift", "add-checklist": "Lägg till checklista", - "add-checklist-item": "Lägg till ett objekt till kontrollista", + "add-checklist-item": "Lägg till objekt i checklista", "add-cover": "Lägg till omslag", "add-label": "Lägg till etikett", "add-list": "Lägg till lista", @@ -89,90 +96,128 @@ "added": "Lades till", "addMemberPopup-title": "Medlemmar", "admin": "Adminstratör", - "admin-desc": "Kan visa och redigera kort, ta bort medlemmar och ändra inställningarna för anslagstavlan.", + "admin-desc": "Kan visa och redigera kort, ta bort medlemmar och ändra inställningarna för tavlan.", "admin-announcement": "Meddelande", - "admin-announcement-active": "Aktivt system-brett meddelande", + "admin-announcement-active": "Aktivt systemövergripande meddelande", "admin-announcement-title": "Meddelande från administratör", - "all-boards": "Alla anslagstavlor", + "all-boards": "Alla tavlor", "and-n-other-card": "Och __count__ annat kort", "and-n-other-card_plural": "Och __count__ andra kort", "apply": "Tillämpa", "app-is-offline": "Läser in, vänligen vänta. Uppdatering av sidan kommer att orsaka förlust av data. Om inläsningen inte fungerar, kontrollera att servern inte har stoppats.", "archive": "Flytta till Arkiv", - "archive-all": "Flytta alla till Arkiv", - "archive-board": "Flytta Anslagstavla till Arkiv", - "archive-card": "Flytta kort till Arkiv", + "archive-all": "Flytta Alla till Arkiv", + "archive-board": "Flytta Tavla till Arkiv", + "archive-card": "Flytta kort till arkiv", "archive-list": "Flytta Lista till Arkiv", - "archive-swimlane": "Flytta simbanan till arkivet", + "archive-swimlane": "Flytta simbana till arkiv", "archive-selection": "Flytta markerad till Arkiv", - "archiveBoardPopup-title": "Flytta Anslagstavla till Arkiv?", + "archiveBoardPopup-title": "Flytta tavla till arkiv?", "archived-items": "Arkiv", - "archived-boards": "Anslagstavlor i Arkiv", - "restore-board": "Återställ anslagstavla", - "no-archived-boards": "Inga anslagstavlor i Arkiv.", + "archived-boards": "Tavlor i arkiv", + "restore-board": "Återställ tavla", + "no-archived-boards": "Inga tavlor i arkiv.", "archives": "Arkiv", "template": "Mall", "templates": "Mallar", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Tilldela medlem", "attached": "bifogad", "attachment": "Bilaga", - "attachment-delete-pop": "Ta bort en bilaga är permanent. Det går inte att ångra.", - "attachmentDeletePopup-title": "Ta bort bilaga?", + "attachment-delete-pop": "Radera en bilaga är permanent. Det går inte att ångra.", + "attachmentDeletePopup-title": "Radera bilaga?", "attachments": "Bilagor", - "auto-watch": "Bevaka automatiskt anslagstavlor när de skapas", - "avatar-too-big": "Avatar är för stor (70KB max)", + "auto-watch": "Bevaka automatiskt tavlor när de skapas", + "avatar-too-big": "Avataren är för stor (max 520 kB)", "back": "Tillbaka", "board-change-color": "Ändra färg", "board-nb-stars": "%s stjärnor", - "board-not-found": "Anslagstavla hittades inte", - "board-private-info": "Denna anslagstavla kommer att vara <strong>privat</strong>.", - "board-public-info": "Denna anslagstavla kommer att vara <strong>officiell</strong>.", - "boardChangeColorPopup-title": "Ändra bakgrund på anslagstavla", - "boardChangeTitlePopup-title": "Byt namn på anslagstavla", + "board-not-found": "Tavla hittades inte", + "board-private-info": "Denna tavla kommer vara <strong>privat</strong>.", + "board-public-info": "Denna tavla kommer vara offentlig.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Ändra tavelbakgrund", + "boardChangeTitlePopup-title": "Byt namn på tavla", "boardChangeVisibilityPopup-title": "Ändra synlighet", "boardChangeWatchPopup-title": "Ändra bevaka", - "boardMenuPopup-title": "Anslagstavlans inställningar", - "boardChangeViewPopup-title": "Anslagstavelsvy", - "boards": "Anslagstavlor", - "board-view": "Anslagstavelsvy", + "boardMenuPopup-title": "Tavelinställningar", + "boardChangeViewPopup-title": "Tavelvy", + "boards": "Tavlor", + "board-view": "Tavelvy", "board-view-cal": "Kalender", "board-view-swimlanes": "Simbanor", - "board-view-collapse": "Kollapsa", + "board-view-collapse": "Fäll ihop", + "board-view-gantt": "Gantt", "board-view-lists": "Listor", - "bucket-example": "Gilla \"att-göra-innan-jag-dör-lista\" till exempel", + "bucket-example": "Som t.ex. \"Kalasplanering\"", "cancel": "Avbryt", - "card-archived": "Detta kort är flyttat till Arkiv.", - "board-archived": "Den här anslagstavlan är flyttad till Arkiv.", + "card-archived": "Detta kort är flyttat till arkiv.", + "board-archived": "Den här tavlan är flyttad till arkiv.", "card-comments-title": "Detta kort har %s kommentar.", - "card-delete-notice": "Ta bort är permanent. Du kommer att förlora alla åtgärder i samband med detta kort.", - "card-delete-pop": "Alla åtgärder kommer att tas bort från aktivitetsflöde och du kommer inte att kunna öppna kortet igen. Det går inte att ångra.", - "card-delete-suggest-archive": "Du kan flytta ett kort för att Arkiv för att ta bort det från anslagstavlan och bevara aktiviteten.", + "card-delete-notice": "Radering är permanent. Du kommer att förlora alla händelser kopplade till detta kort.", + "card-delete-pop": "Alla händelser kommer att tas bort från aktivitetsflödet och du kommer inte att kunna öppna kortet igen. Det går inte att ångra.", + "card-delete-suggest-archive": "Du kan flytta ett kort till Arkiv för att ta bort det från tavlan och bevara aktiviteten.", "card-due": "Förfaller", "card-due-on": "Förfaller på", "card-spent": "Spenderad tid", - "card-edit-attachments": "Redigera bilaga", + "card-edit-attachments": "Redigera bilagor", "card-edit-custom-fields": "Redigera anpassade fält", "card-edit-labels": "Redigera etiketter", "card-edit-members": "Redigera medlemmar", - "card-labels-title": "Ändra etiketter för kortet.", - "card-members-title": "Lägg till eller ta bort medlemmar av anslagstavlan från kortet.", - "card-start": "Börja", - "card-start-on": "Börja med", - "cardAttachmentsPopup-title": "Bifoga från", + "card-labels-title": "Ändra etiketterna för kortet.", + "card-members-title": "Lägg till eller ta bort medlemmar i tavlan från kortet.", + "card-start": "Påbörjades", + "card-start-on": "Börjar med", + "cardAttachmentsPopup-title": "Bifoga Från", "cardCustomField-datePopup-title": "Ändra datum", "cardCustomFieldsPopup-title": "Redigera anpassade fält", - "cardDeletePopup-title": "Ta bort kort?", - "cardDetailsActionsPopup-title": "Kortåtgärder", + "cardStartVotingPopup-title": "Påbörja en omröstning", + "positiveVoteMembersPopup-title": "Förespråkare", + "negativeVoteMembersPopup-title": "Opponenter", + "card-edit-voting": "Redigera omröstning", + "editVoteEndDatePopup-title": "Ändra slutdatum för omröstning", + "allowNonBoardMembers": "Tillåt alla inloggade användare", + "vote-question": "Omröstningsfråga", + "vote-public": "Visa vem som röstade på vad", + "vote-for-it": "för", + "vote-against": "emot", + "deleteVotePopup-title": "Radera omröstning?", + "vote-delete-pop": "Radering är permanent. Du kommer förlora alla händelser kopplade till denna röstning.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Radera kort?", + "cardDetailsActionsPopup-title": "Korthändelser", "cardLabelsPopup-title": "Etiketter", "cardMembersPopup-title": "Medlemmar", - "cardMorePopup-title": "Mera", + "cardMorePopup-title": "Mer", "cardTemplatePopup-title": "Skapa mall", "cards": "Kort", "cards-count": "Kort", + "cards-count-one": "Kort", "casSignIn": "Logga in med CAS", "cardType-card": "Kort", "cardType-linkedCard": "Länkat kort", - "cardType-linkedBoard": "Länkad anslagstavla", + "cardType-linkedBoard": "Länkad tavla", "change": "Ändra", "change-avatar": "Ändra avatar", "change-password": "Ändra lösenord", @@ -184,13 +229,14 @@ "changePermissionsPopup-title": "Ändra behörigheter", "changeSettingsPopup-title": "Ändra inställningar", "subtasks": "Deluppgifter", - "checklists": "Kontrollistor", - "click-to-star": "Klicka för att stjärnmärka denna anslagstavla.", - "click-to-unstar": "Klicka för att ta bort stjärnmärkningen från denna anslagstavla.", - "clipboard": "Urklipp eller dra och släpp", + "checklists": "Checklistor", + "click-to-star": "Klicka för att stjärnmärka denna tavla.", + "click-to-unstar": "Klicka för att ta bort stjärnmärkningen från denna tavla.", + "clipboard": "Urklipp eller dra & släpp", "close": "Stäng", - "close-board": "Stäng anslagstavla", - "close-board-pop": "Du kommer att kunna återställa anslagstavlan genom att klicka på knappen \"Arkiv\" från hemrubriken.", + "close-board": "Stäng tavla", + "close-board-pop": "Du kommer att kunna återställa tavlan genom att klicka på knappen \"Arkiv\" i huvudmenyn.", + "close-card": "Close Card", "color-black": "svart", "color-blue": "blå", "color-crimson": "mörkröd", @@ -205,7 +251,7 @@ "color-navy": "marinblå", "color-orange": "orange", "color-paleturquoise": "turkos", - "color-peachpuff": "ersika", + "color-peachpuff": "persika", "color-pink": "rosa", "color-plum": "lila", "color-purple": "lila", @@ -216,7 +262,7 @@ "color-slateblue": "skifferblå", "color-white": "vit", "color-yellow": "gul", - "unset-color": "Urkoppla", + "unset-color": "Ta bort", "comment": "Kommentera", "comment-placeholder": "Skriv kommentar", "comment-only": "Kommentera endast", @@ -224,7 +270,7 @@ "no-comments": "Inga kommentarer", "no-comments-desc": "Kan inte se kommentarer och aktiviteter.", "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker-desc": "Kan endast flytta kort, tilldela sig själv till kort och kommentera.", "computer": "Dator", "confirm-subtask-delete-dialog": "Är du säker på att du vill radera deluppgift?", "confirm-checklist-delete-dialog": "Är du säker på att du vill radera checklista?", @@ -232,20 +278,22 @@ "linkCardPopup-title": "Länka kort", "searchElementPopup-title": "Sök", "copyCardPopup-title": "Kopiera kort", - "copyChecklistToManyCardsPopup-title": "Kopiera checklist-mallen till flera kort", + "copyChecklistToManyCardsPopup-title": "Kopiera Checklistmallen till Flera Kort", "copyChecklistToManyCardsPopup-instructions": "Destinationskorttitlar och beskrivningar i detta JSON-format", "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Första kortets titel\", \"description\":\"Första kortets beskrivning\"}, {\"title\":\"Andra kortets titel\",\"description\":\"Andra kortets beskrivning\"},{\"title\":\"Sista kortets titel\",\"description\":\"Sista kortets beskrivning\"} ]", "create": "Skapa", - "createBoardPopup-title": "Skapa anslagstavla", - "chooseBoardSourcePopup-title": "Importera anslagstavla", + "createBoardPopup-title": "Skapa tavla", + "chooseBoardSourcePopup-title": "Importera tavla", "createLabelPopup-title": "Skapa etikett", "createCustomField": "Skapa fält", "createCustomFieldPopup-title": "Skapa fält", "current": "aktuell", - "custom-field-delete-pop": "Det går inte att ångra. Detta tar bort det här anpassade fältet från alla kort och förstör dess historia.", + "custom-field-delete-pop": "Det går inte att ångra. Detta tar bort det här anpassade fältet från alla kort och förstör dess historik.", "custom-field-checkbox": "Kryssruta", + "custom-field-currency": "Valuta", + "custom-field-currency-option": "Valutakod", "custom-field-date": "Datum", - "custom-field-dropdown": "Rullgardingsmeny", + "custom-field-dropdown": "Rullgardinsmeny", "custom-field-dropdown-none": "(inga)", "custom-field-dropdown-options": "Listalternativ", "custom-field-dropdown-options-placeholder": "Tryck på enter för att lägga till fler alternativ", @@ -257,97 +305,127 @@ "decline": "Nedgång", "default-avatar": "Standard avatar", "delete": "Ta bort", - "deleteCustomFieldPopup-title": "Ta bort anpassade fält?", - "deleteLabelPopup-title": "Ta bort etikett?", + "deleteCustomFieldPopup-title": "Radera anpassat fält?", + "deleteLabelPopup-title": "Radera etikett?", "description": "Beskrivning", - "disambiguateMultiLabelPopup-title": "Otvetydig etikettåtgärd", - "disambiguateMultiMemberPopup-title": "Otvetydig medlemsåtgärd", - "discard": "Kassera", + "disambiguateMultiLabelPopup-title": "Precisera etiketthändelse", + "disambiguateMultiMemberPopup-title": "Precisera medlemshändelse", + "discard": "Ignorera", "done": "Färdig", "download": "Hämta", "edit": "Redigera", "edit-avatar": "Ändra avatar", "edit-profile": "Redigera profil", - "edit-wip-limit": "Redigera WIP-gränsen", + "edit-wip-limit": "Redigera WIP-gräns", "soft-wip-limit": "Mjuk WIP-gräns", "editCardStartDatePopup-title": "Ändra startdatum", "editCardDueDatePopup-title": "Ändra förfallodatum", "editCustomFieldPopup-title": "Redigera fält", "editCardSpentTimePopup-title": "Ändra spenderad tid", "editLabelPopup-title": "Ändra etikett", - "editNotificationPopup-title": "Redigera avisering", + "editNotificationPopup-title": "Redigera notis", "editProfilePopup-title": "Redigera profil", "email": "E-post", - "email-enrollAccount-subject": "Ett konto skapas för dig på __siteName__", - "email-enrollAccount-text": "Hej __user__,\n\nFör att börja använda tjänsten, klicka på länken nedan.\n\n__url__\n\nTack.", + "email-enrollAccount-subject": "Ett konto skapat för dig på __siteName__", + "email-enrollAccount-text": "Hej __user__,\n\nFör att börja använda tjänsten, klicka på länken nedan.\n\n__url__\n\nTack!", "email-fail": "Sändning av e-post misslyckades", "email-fail-text": "Ett fel vid försök att skicka e-post", "email-invalid": "Ogiltig e-post", "email-invite": "Bjud in via e-post", "email-invite-subject": "__inviter__ skickade dig en inbjudan", - "email-invite-text": "Bästa __user__,\n\n__inviter__ inbjuder dig till anslagstavlan \"__board__\" för samarbete.\n\nFölj länken nedan:\n\n__url__\n\nTack.", + "email-invite-text": "Kära __user__,\n\n__inviter__ inbjuder dig till tavlan \"__board__\" för samarbete.\n\nFölj länken nedan:\n\n__url__\n\nTack.", "email-resetPassword-subject": "Återställa lösenordet för __siteName__", - "email-resetPassword-text": "Hej __user__,\n\nFör att återställa ditt lösenord, klicka på länken nedan.\n\n__url__\n\nTack.", + "email-resetPassword-text": "Hej __user__,\n\nFör att återställa ditt lösenord, klicka på länken nedan.\n\n__url__\n\nTack!", "email-sent": "E-post skickad", "email-verifyEmail-subject": "Verifiera din e-post adress på __siteName__", - "email-verifyEmail-text": "Hej __user__,\n\nFör att verifiera din konto e-post, klicka på länken nedan.\n\n__url__\n\nTack.", + "email-verifyEmail-text": "Hej __user__,\n\nFör att verifiera din konto e-post, klicka på länken nedan.\n\n__url__\n\nTack!", "enable-wip-limit": "Aktivera WIP-gräns", - "error-board-doesNotExist": "Denna anslagstavla finns inte", - "error-board-notAdmin": "Du måste vara administratör för denna anslagstavla för att göra det", - "error-board-notAMember": "Du måste vara medlem i denna anslagstavla för att göra det", + "error-board-doesNotExist": "Denna tavla finns inte", + "error-board-notAdmin": "Du måste vara administratör för denna tavla för att göra det", + "error-board-notAMember": "Du måste vara medlem i denna tavla för att göra det", "error-json-malformed": "Din text är inte giltigt JSON", "error-json-schema": "Din JSON data inkluderar inte korrekt information i rätt format", + "error-csv-schema": "Din CSV(Comma Separated Values)/TSV (Tab Separated Values) innehåller inte korrekt information i rätt format.", "error-list-doesNotExist": "Denna lista finns inte", "error-user-doesNotExist": "Denna användare finns inte", "error-user-notAllowSelf": "Du kan inte bjuda in dig själv", "error-user-notCreated": "Den här användaren har inte skapats", "error-username-taken": "Detta användarnamn är redan taget", + "error-orgname-taken": "Organisations namnet är upptaget.", + "error-teamname-taken": "Detta team namn är redan taget.", "error-email-taken": "E-post har redan tagits", - "export-board": "Exportera anslagstavla", + "export-board": "Exportera tavla", + "export-board-json": "Exportera tavla till JSON", + "export-board-csv": "Expoertera tavla till CSV", + "export-board-tsv": "Exportera tavla till TSV", + "export-board-excel": "Exportera tavla till Excel", + "user-can-not-export-excel": "Användare kan inte exportera till Excel", + "export-board-html": "Exportera tavla till HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Exportera tavla", + "exportCardPopup-title": "Export card", "sort": "Sortera", "sort-desc": "Klicka för att sortera listan", "list-sort-by": "Sortera listan efter:", "list-label-modifiedAt": "Sista åtkomsttid", "list-label-title": "Namn på listan", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", + "list-label-sort": "Din Manuella Ordning", + "list-label-short-modifiedAt": "(S)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", "filter": "Filtrera", "filter-cards": "Filtrera kort eller listor", + "filter-dates-label": "Filtrera efter datum", + "filter-no-due-date": "Inget förfallodatum", + "filter-overdue": "Försenad", + "filter-due-today": "Förfaller idag", + "filter-due-this-week": "Förfaller denna vecka", + "filter-due-tomorrow": "Förfaller imorgon", "list-filter-label": "Filtrera lista efter titel", "filter-clear": "Rensa filter", + "filter-labels-label": "Filtrera på etikett", "filter-no-label": "Ingen etikett", + "filter-member-label": "Filtrera på medlem", "filter-no-member": "Ingen medlem", + "filter-assignee-label": "Filtrera på tilldelad till", + "filter-no-assignee": "Inte tilldelad", + "filter-custom-fields-label": "Filtrera på anpassade fält", "filter-no-custom-fields": "Inga anpassade fält", "filter-show-archive": "Visa arkiverade listor", "filter-hide-empty": "Dölj tomma listor", "filter-on": "Filter är på", - "filter-on-desc": "Du filtrerar kort på denna anslagstavla. Klicka här för att redigera filter.", + "filter-on-desc": "Du filtrerar kort på denna tavla. Klicka här för att redigera filter.", "filter-to-selection": "Filter till val", + "other-filters-label": "Andra filter", "advanced-filter-label": "Avancerat filter", "advanced-filter-description": "Avancerade filter låter dig skriva en sträng innehållande följande operatorer: == != <= >= && || ( ). Ett mellanslag används som separator mellan operatorerna. Du kan filtrera alla specialfält genom att skriva dess namn och värde. Till exempel: Fält1 == Vårde1. Notera: om fälten eller värden innehåller mellanrum behöver du innesluta dem med enkla citatstecken. Till exempel: 'Fält 1' == 'Värde 1'. För att skippa enkla kontrolltecken (' \\/) kan du använda \\. Till exempel: Fält1 == I\\'m. Du kan även kombinera fler villkor. TIll exempel: F1 == V1 || F1 == V2. Vanligtvis läses operatorerna från vänster till höger. Du kan ändra ordning genom att använda paranteser. TIll exempel: F1 == V1 && ( F2 == V2 || F2 == V3 ). Du kan även söka efter textfält med hjälp av regex: F1 == /Tes.*/i", "fullname": "Namn", "header-logo-title": "Gå tillbaka till din anslagstavlor-sida.", "hide-system-messages": "Dölj systemmeddelanden", - "headerBarCreateBoardPopup-title": "Skapa anslagstavla", + "headerBarCreateBoardPopup-title": "Skapa tavla", "home": "Hem", "import": "Importera", + "impersonate-user": "Imitera användare", "link": "Länk", - "import-board": "importera anslagstavla", - "import-board-c": "Importera anslagstavla", - "import-board-title-trello": "Importera anslagstavla från Trello", - "import-board-title-wekan": "Importera anslagstavla från tidigare export", - "import-sandstorm-backup-warning": "Ta inte bort data som du importerar från exporterad original-tavla eller Trello innan du kontrollerar att det här spannet stänger och öppnas igen, eller får du felmeddelandet Anslagstavla hittades inte, det vill säga dataförlust.", - "import-sandstorm-warning": "Importerad anslagstavla raderar all befintlig data på anslagstavla och ersätter den med importerat anslagstavla.", + "import-board": "importera tavla", + "import-board-c": "Importera tavla", + "import-board-title-trello": "Importera tavla från Trello", + "import-board-title-wekan": "Importera tavla från tidigare export", + "import-board-title-csv": "Importera tavla från CSV/TSV", "from-trello": "Från Trello", "from-wekan": "Från tidigare export", - "import-board-instruction-trello": "I din Trello-anslagstavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.", - "import-board-instruction-wekan": "På din anslagstavla, gå till \"Meny\", sedan \"Exportera anslagstavla\" och kopiera texten i den hämtade filen.", - "import-board-instruction-about-errors": "Om du får fel vid import av anslagstavla, ibland importerar fortfarande fungerar, och styrelsen är på alla sidor för anslagstavlor.", + "from-csv": "Från CSV/TSV", + "import-board-instruction-trello": "I din Trello-tavla, gå till 'Meny', sedan 'Mera', 'Skriv ut och exportera', 'Exportera JSON' och kopiera den resulterande text.", + "import-board-instruction-csv": "Klistra in Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", + "import-board-instruction-wekan": "På din tavla, gå till \"Meny\", sedan \"Exportera tavla\" och kopiera texten i den hämtade filen.", + "import-board-instruction-about-errors": "Om du får ett fel vid import av tavlor kan importen ändå gått bra och tavlan finns på översiktssidan.", "import-json-placeholder": "Klistra in giltigt JSON data här", + "import-csv-placeholder": "Klistra in CSV/TSV data här", "import-map-members": "Kartlägg medlemmar", - "import-members-map": "Din importerade anslagstavla har några medlemmar. Vänligen kartlägg medlemmarna du vill importera till dina användare", + "import-members-map": "Din importerade tavla har några medlemmar. Vänligen matcha medlemmarna du vill importera mot dina användare", + "import-members-map-note": "Notera: Ommappade medlemmar kommer tilldelas den nuvarande användaren. ", "import-show-user-mapping": "Granska medlemskartläggning", "import-user-select": "Välj din befintliga användare du vill använda som den här medlemmen", "importMapMembersAddPopup-title": "Välj medlem", @@ -357,31 +435,35 @@ "invalid-time": "Ogiltig tid", "invalid-user": "Ogiltig användare", "joined": "gick med", - "just-invited": "Du blev nyss inbjuden till denna anslagstavla", + "just-invited": "Du blev nyss inbjuden till denna tavla", "keyboard-shortcuts": "Tangentbordsgenvägar", "label-create": "Skapa etikett", "label-default": "%s etikett (standard)", - "label-delete-pop": "Det finns ingen ångra. Detta tar bort denna etikett från alla kort och förstöra dess historik.", + "label-delete-pop": "Det går inte att ångra. Detta tar bort denna etikett från alla kort och förstör dess historik.", "labels": "Etiketter", "language": "Språk", "last-admin-desc": "Du kan inte ändra roller för det måste finnas minst en administratör.", - "leave-board": "Lämna anslagstavla", - "leave-board-pop": "Är du säker på att du vill lämna __boardTitle__? Du kommer att tas bort från alla kort på den här anslagstavlan.", - "leaveBoardPopup-title": "Lämna anslagstavla ?", + "leave-board": "Lämna tavla", + "leave-board-pop": "Är du säker på att du vill lämna __boardTitle__? Du kommer att tas bort från alla kort på den här tavlan.", + "leaveBoardPopup-title": "Lämna tavla?", "link-card": "Länk till detta kort", - "list-archive-cards": "Flytta alla kort i den här listan till Arkiv", - "list-archive-cards-pop": "Detta kommer att ta bort alla kort i denna lista från anslagstavlan. För att visa kort i Arkiv och få dem tillbaka till anslagstavlan, klicka på \"Meny\" > \"Arkiv\".", + "list-archive-cards": "Flytta alla kort i den här listan till arkiv", + "list-archive-cards-pop": "Detta kommer att ta bort alla kort i denna lista från tavlan. För att visa kort i Arkiv och få dem tillbaka till tavlan, klicka på \"Meny\" > \"Arkiv\".", "list-move-cards": "Flytta alla kort i denna lista", "list-select-cards": "Välj alla kort i denna lista", "set-color-list": "Ange färg", "listActionPopup-title": "Liståtgärder", - "swimlaneActionPopup-title": "Simbana-åtgärder", + "settingsUserPopup-title": "Användarinställningar", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Inställningar för simbana", "swimlaneAddPopup-title": "Lägg till en simbana nedan", "listImportCardPopup-title": "Importera ett Trello kort", + "listImportCardsTsvPopup-title": "Importera Excel CSV/TSV", "listMorePopup-title": "Mera", "link-list": "Länk till den här listan", - "list-delete-pop": "Alla åtgärder kommer att tas bort från aktivitetsmatningen och du kommer inte att kunna återställa listan. Det går inte att ångra.", - "list-delete-suggest-archive": "Du kan flytta en lista till Arkiv för att ta bort den från anslagstavlan och bevara aktiviteten.", + "list-delete-pop": "Alla händelser kommer att tas bort från aktivitetsflödet och du kommer inte att kunna återställa listan. Det går inte att ångra.", + "list-delete-suggest-archive": "Du kan flytta en lista till Arkiv för att ta bort den från tavlan och bevara aktiviteten.", "lists": "Listor", "swimlanes": "Simbanor", "log-out": "Logga ut", @@ -396,14 +478,16 @@ "moveCardToTop-title": "Flytta högst upp", "moveSelectionPopup-title": "Flytta vald", "multi-selection": "Flerval", + "multi-selection-label": "Ange etikett för val", + "multi-selection-member": "Ange medlem för val", "multi-selection-on": "Flerval är på", "muted": "Tystad", - "muted-info": "Du kommer aldrig att meddelas om eventuella ändringar i denna anslagstavla", - "my-boards": "Mina anslagstavlor", + "muted-info": "Du kommer aldrig att få notiser om eventuella ändringar i denna tavla", + "my-boards": "Mina tavlor", "name": "Namn", "no-archived-cards": "Inga kort i Arkiv.", "no-archived-lists": "Inga listor i Arkiv.", - "no-archived-swimlanes": "Inga simbanor i arkivet.", + "no-archived-swimlanes": "Inga simbanor i arkiv.", "no-results": "Inga reslutat", "normal": "Normal", "normal-desc": "Kan se och redigera kort. Kan inte ändra inställningar.", @@ -421,28 +505,29 @@ "previewAttachedImagePopup-title": "Förhandsvisning", "previewClipboardImagePopup-title": "Förhandsvisning", "private": "Privat", - "private-desc": "Denna anslagstavla är privat. Endast personer tillagda till anslagstavlan kan se och redigera den.", + "private-desc": "Denna tavla är privat. Endast personer tillagda i tavlan kan se och redigera den.", "profile": "Profil", - "public": "Officiell", - "public-desc": "Denna anslagstavla är offentlig. Den är synligt för alla med länken och kommer att dyka upp i sökmotorer som Google. Endast personer tillagda till anslagstavlan kan redigera.", - "quick-access-description": "Stjärnmärk en anslagstavla för att lägga till en genväg i detta fält.", - "remove-cover": "Ta bort omslag", - "remove-from-board": "Ta bort från anslagstavla", + "public": "Offentlig", + "public-desc": "Denna tavla är offentlig. Den är synlig för alla med länken och kommer att dyka upp i sökmotorer som Google. Endast personer tillagda i tavlan kan redigera.", + "quick-access-description": "Stjärnmärk en tavla för att lägga till som genväg här.", + "remove-cover": "Radera omslag", + "remove-from-board": "Ta bort från tavla", "remove-label": "Ta bort etikett", "listDeletePopup-title": "Ta bort lista", "remove-member": "Ta bort medlem", "remove-member-from-card": "Ta bort från kort", - "remove-member-pop": "Ta bort __name__ (__username__) från __boardTitle__? Medlemmen kommer att bli borttagen från alla kort i denna anslagstavla. De kommer att få en avisering.", + "remove-member-pop": "Ta bort __name__ (__username__) från __boardTitle__? Medlemmen kommer att bli borttagen från alla kort i denna tavla. De kommer att få en notis.", "removeMemberPopup-title": "Ta bort medlem?", "rename": "Byt namn", - "rename-board": "Byt namn på anslagstavla", + "rename-board": "Byt namn på tavla", "restore": "Återställ", "save": "Spara", "search": "Sök", "rules": "Regler", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", + "search-cards": "Sök i kort- och listtitlar, beskrivningar och anpassade fält på denna tavla", "search-example": "Text att söka efter?", "select-color": "Välj färg", + "select-board": "Välj tavla", "set-wip-limit-value": "Ange en gräns för det maximala antalet uppgifter i den här listan", "setWipLimitPopup-title": "Ställ in WIP-gräns", "shortcut-assign-self": "Tilldela dig nuvarande kort", @@ -453,17 +538,18 @@ "shortcut-filter-my-cards": "Filtrera mina kort", "shortcut-show-shortcuts": "Ta fram denna genvägslista", "shortcut-toggle-filterbar": "Växla filtrets sidofält", + "shortcut-toggle-searchbar": "Växla sökfältet", "shortcut-toggle-sidebar": "Växla anslagstavlans sidofält", "show-cards-minimum-count": "Visa kortantal om listan innehåller mer än", "sidebar-open": "Stäng sidofält", "sidebar-close": "Stäng sidofält", "signupPopup-title": "Skapa ett konto", - "star-board-title": "Klicka för att stjärnmärka denna anslagstavla. Den kommer att visas högst upp på din lista över anslagstavlor.", + "star-board-title": "Klicka för att stjärnmärka denna tavla. Den kommer att visas högst upp på din lista över tavlor.", "starred-boards": "Stjärnmärkta anslagstavlor", "starred-boards-description": "Stjärnmärkta anslagstavlor visas högst upp på din lista över anslagstavlor.", "subscribe": "Prenumenera", "team": "Grupp", - "this-board": "denna anslagstavla", + "this-board": "denna tavla", "this-card": "detta kort", "spent-time-hours": "Spenderad tid (timmar)", "overtime-hours": "Övertid (timmar)", @@ -481,18 +567,26 @@ "upload": "Ladda upp", "upload-avatar": "Ladda upp en avatar", "uploaded-avatar": "Laddade upp en avatar", + "custom-top-left-corner-logo-image-url": "Länk till anpassad logotypbild", + "custom-top-left-corner-logo-link-url": "Länk för anpassad logotyp", + "custom-top-left-corner-logo-height": "Anpassad logotyphöjd uppe till vänster. Standard: 27", + "custom-login-logo-image-url": "Länk till bild för anpassad logotyp på inloggningssidan", + "custom-login-logo-link-url": "Länk för anpassad logotyp på inloggningssidan", + "text-below-custom-login-logo": "Text under anpassad logga för inloggning", + "automatic-linked-url-schemes": "Anpassade URL-scheman som ska vara klickbara automatiskt. Ett URL-schema per rad", "username": "Änvandarnamn", + "import-usernames": "Importera användarnamn", "view-it": "Visa det", "warn-list-archived": "varning: detta kort finns i en lista i Arkiv", "watch": "Bevaka", "watching": "Bevaka", - "watching-info": "Du kommer att meddelas om alla ändringar på denna anslagstavla", + "watching-info": "Du kommer att få notis om alla ändringar på denna tavla", "welcome-board": "Välkomstanslagstavla", "welcome-swimlane": "Milstolpe 1", "welcome-list1": "Grunderna", "welcome-list2": "Avancerad", "card-templates-swimlane": "Kortmallar", - "list-templates-swimlane": "Listmalla", + "list-templates-swimlane": "Listmallar", "board-templates-swimlane": "Tavelmallar", "what-to-do": "Vad vill du göra?", "wipLimitErrorPopup-title": "Ogiltig WIP-gräns", @@ -519,19 +613,19 @@ "send-smtp-test": "Skicka ett prov e-postmeddelande till dig själv", "invitation-code": "Inbjudningskod", "email-invite-register-subject": "__inviter__ skickade dig en inbjudan", - "email-invite-register-text": "Kära__user__,\n\n__inviter__ bjuder in dig att samarbeta på kanban-anslagstavlan.\n\nFölj länken nedan:\n__url__\n\nDin inbjudningskod är: __icode__\n\nTack!", - "email-smtp-test-subject": "SMTP test-email", + "email-invite-register-text": "Kära__user__,\n\n__inviter__ bjuder in dig att samarbeta på kanban-tavla.\n\nFölj länken nedan:\n__url__\n\nDin inbjudningskod är: __icode__\n\nTack!", + "email-smtp-test-subject": "SMTP Test E-post", "email-smtp-test-text": "Du har skickat ett e-postmeddelande", "error-invitation-code-not-exist": "Inbjudningskod finns inte", "error-notAuthorized": "Du är inte behörig att se den här sidan.", "webhook-title": "Namn på webhook", "webhook-token": "Token (valfritt för autentisering)", "outgoing-webhooks": "Utgående Webhookar", - "bidirectional-webhooks": "Two-Way Webhooks", + "bidirectional-webhooks": "Dubbelriktade Webhookar", "outgoingWebhooksPopup-title": "Utgående Webhookar", - "boardCardTitlePopup-title": "Korttitelfiler", - "disable-webhook": "Avaktivera denna webhook", - "global-webhook": "Globala webhooks", + "boardCardTitlePopup-title": "Korttitelfilter", + "disable-webhook": "Avaktivera Denna Webhook", + "global-webhook": "Globala Webhooks", "new-outgoing-webhook": "Ny utgående webhook", "no-name": "(Okänd)", "Node_version": "Nodversion", @@ -553,7 +647,8 @@ "minutes": "minuter", "seconds": "sekunder", "show-field-on-card": "Visa detta fält på kort", - "automatically-field-on-card": "Skapa automatiskt fält till alla kort", + "automatically-field-on-card": "Lägg till fält till nytt kort", + "always-field-on-card": "Lägg till fält till alla kort", "showLabel-field-on-card": "Visa fältetikett på minikort", "yes": "Ja", "no": "Nej", @@ -561,11 +656,12 @@ "accounts-allowEmailChange": "Tillåt e-poständring", "accounts-allowUserNameChange": "Tillåt användarnamnändring", "createdAt": "Skapad vid", + "modifiedAt": "Senast ändrad", "verified": "Verifierad", "active": "Aktiv", "card-received": "Mottagen", "card-received-on": "Mottagen den", - "card-end": "Sluta", + "card-end": "Avslutades", "card-end-on": "Slutar den", "editCardReceivedDatePopup-title": "Ändra mottagningsdatum", "editCardEndDatePopup-title": "Ändra slutdatum", @@ -575,60 +671,64 @@ "setListColorPopup-title": "Välj en färg", "assigned-by": "Tilldelad av", "requested-by": "Efterfrågad av", - "board-delete-notice": "Borttagningen är permanent. Du kommer förlora alla listor, kort och händelser kopplade till den här anslagstavlan.", - "delete-board-confirm-popup": "Alla listor, kort, etiketter och aktiviteter kommer tas bort och du kommer inte kunna återställa anslagstavlans innehåll. Det går inte att ångra.", - "boardDeletePopup-title": "Ta bort anslagstavla?", - "delete-board": "Ta bort anslagstavla", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Radering är permanent. Du kommer förlora alla listor, kort och händelser kopplade till den här tavlan.", + "delete-board-confirm-popup": "Alla listor, kort, etiketter och aktiviteter kommer tas bort och du kommer inte kunna återställa tavlans innehåll. Det går inte att ångra.", + "boardDeletePopup-title": "Ta bort tavla?", + "delete-board": "Ta bort tavla", "default-subtasks-board": "Deluppgifter för __board__ board", "default": "Standard", "queue": "Kö", "subtask-settings": "Deluppgift inställningar", "card-settings": "Kortinställningar", - "boardSubtaskSettingsPopup-title": "Deluppgiftsinställningar för anslagstavla", + "boardSubtaskSettingsPopup-title": "Tavelinställningar för Deluppgifter", "boardCardSettingsPopup-title": "Kortinställningar", - "deposit-subtasks-board": "Insättnings deluppgifter på denna anslagstavla:", + "deposit-subtasks-board": "Lägg till deluppgifter till denna tavla:", "deposit-subtasks-list": "Landningslista för deluppgifter deponerade här:", - "show-parent-in-minicard": "Visa förälder i minikort:", + "show-parent-in-minicard": "Visa överordnad i minikort:", "prefix-with-full-path": "Prefix med fullständig sökväg", - "prefix-with-parent": "Prefix med förälder", + "prefix-with-parent": "Prefix med överordnad", "subtext-with-full-path": "Undertext med fullständig sökväg", - "subtext-with-parent": "Undertext med förälder", + "subtext-with-parent": "Undertext med överordnad", "change-card-parent": "Ändra kortets förälder", "parent-card": "Ovankort", - "source-board": "Källa för anslagstavla", - "no-parent": "Visa inte förälder", + "source-board": "Tavelkälla", + "no-parent": "Visa inte överordnad", "activity-added-label": "lade till etiketten '%s' till %s", "activity-removed-label": "tog bort etiketten '%s' från %s", "activity-delete-attach": "raderade en bilaga från %s", "activity-added-label-card": "lade till etiketten \"%s\"", "activity-removed-label-card": "tog bort etiketten \"%s\"", "activity-delete-attach-card": "tog bort en bilaga", - "activity-set-customfield": "ställ in anpassat fält '%s' till '%s' i %s", - "activity-unset-customfield": "Koppla bort anpassat fält '%s' i %s", + "activity-set-customfield": "ställ in anpassat fält \"%s\" till \"%s\" i %s", + "activity-unset-customfield": "koppla bort anpassat fält \"%s\" i %s", "r-rule": "Regel", "r-add-trigger": "Lägg till utlösare", "r-add-action": "Lägg till åtgärd", - "r-board-rules": "Regler för anslagstavla", + "r-board-rules": "Tavelregler", "r-add-rule": "Lägg till regel", "r-view-rule": "Visa regel", "r-delete-rule": "Ta bort regel", "r-new-rule-name": "Ny titel på regel", "r-no-rules": "Inga regler", + "r-trigger": "Trigger", + "r-action": "Åtgärd", "r-when-a-card": "När ett kort", "r-is": "är", - "r-is-moved": "är flyttad", - "r-added-to": "tillagd till", + "r-is-moved": "är flyttat", + "r-added-to": "Tillagd till", "r-removed-from": "Borttagen från", - "r-the-board": "anslagstavlan", + "r-the-board": "tavlan", "r-list": "lista", + "list": "Lista", "set-filter": "Ställ in filter", - "r-moved-to": "Flyttad till", + "r-moved-to": "Flyttat till", "r-moved-from": "Flyttad från", "r-archived": "Flyttad till Arkiv", "r-unarchived": "Återställd från Arkiv", "r-a-card": "ett kort", "r-when-a-label-is": "När en etikett är", - "r-when-the-label": "När etiketten är", + "r-when-the-label": "När etiketten", "r-list-name": "listnamn", "r-when-a-member": "När en medlem är", "r-when-the-member": "När medlemmen", @@ -638,7 +738,7 @@ "r-when-the-checklist": "När checklistan", "r-completed": "Avslutad", "r-made-incomplete": "Gjord ofullständig", - "r-when-a-item": "När ett checklistobjekt ä", + "r-when-a-item": "När ett checklistobjekt är", "r-when-the-item": "När checklistans objekt", "r-checked": "Kryssad", "r-unchecked": "Okryssad", @@ -665,6 +765,7 @@ "r-of-checklist": "av checklistan", "r-send-email": "Skicka ett e-postmeddelande", "r-to": "till", + "r-of": "av", "r-subject": "änme", "r-rule-details": "Regeldetaljer", "r-d-move-to-top-gen": "Flytta kort till toppen av sin lista", @@ -675,7 +776,7 @@ "r-d-send-email-to": "till", "r-d-send-email-subject": "ämne", "r-d-send-email-message": "meddelande", - "r-d-archive": "Flytta kort till Arkiv", + "r-d-archive": "Flytta kort till arkiv", "r-d-unarchive": "Återställ kortet från Arkiv", "r-d-add-label": "Lägg till etikett", "r-d-remove-label": "Ta bort etikett", @@ -699,7 +800,7 @@ "r-add-swimlane": "Lägg till simbana", "r-swimlane-name": "Simbanans namn", "r-board-note": "Notera: lämna ett fält tomt för att matcha alla möjliga värden.", - "r-checklist-note": "Notera: Objekt i en checklista måste skrivas som kommaseparerade objekt", + "r-checklist-note": "Notera: objekt i en checklista måste skrivas som kommaseparerade värden.", "r-when-a-card-is-moved": "När ett kort flyttas till en annan lista", "r-set": "Ange", "r-update": "Uppdatera", @@ -724,10 +825,12 @@ "error-ldap-login": "Ett fel uppstod när du försökte logga in", "display-authentication-method": "Visa autentiseringsmetod", "default-authentication-method": "Standard autentiseringsmetod", - "duplicate-board": "Dubblett anslagstavla", + "duplicate-board": "Duplicera tavla", + "org-number": "Antalet organisationer är:", + "team-number": "Antalet team är:", "people-number": "Antalet personer är:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "swimlaneDeletePopup-title": "Radera simbana?", + "swimlane-delete-pop": "Alla händelser kommer att tas bort från aktivitetsflödet och du kommer inte att kunna återställa simbanan. Det går inte att ångra.", "restore-all": "Återställ alla", "delete-all": "Ta bort alla", "loading": "Läser in, var god vänta.", @@ -750,6 +853,8 @@ "act-duenow": "påminde om den aktuella förfallotiden (__timeValue__) av __card__ är nu", "act-atUserComment": "Du omnämndes i [__board__] __list__/__card__", "delete-user-confirm-popup": "Är du säker på att du vill ta bort det här kontot? Det går inte att ångra sig.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Tillåt användare att själv ta bort sina konton", "hide-minicard-label-text": "Dölj etikett för minikort", "show-desktop-drag-handles": "Visa greppytor i desktop", @@ -758,12 +863,200 @@ "addmore-detail": "Lägg till detaljerad beskrivning", "show-on-card": "Visa på kort", "new": "Ny", + "editOrgPopup-title": "Editera Oraganisation.", + "newOrgPopup-title": "Ny Organisation.", + "editTeamPopup-title": "Redigera Grupp", + "newTeamPopup-title": "Ny Grupp", "editUserPopup-title": "Redigera användare", "newUserPopup-title": "Ny användare", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "notifications": "Notiser", + "view-all": "Visa Allt", + "filter-by-unread": "Filtrera efter Oläst", + "mark-all-as-read": "Markera alla som lästa", + "remove-all-read": "Ta bort alla lästa", + "allow-rename": "Tillåt Namnändring", + "allowRenamePopup-title": "Tillåt Namnändring", + "start-day-of-week": "Ange veckans första dag", + "monday": "Måndag", + "tuesday": "Tisdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "saturday": "Lördag", + "sunday": "Söndag", + "status": "Status", + "swimlane": "Simbana", + "owner": "Ägare", + "last-modified-at": "Senast ändrad", + "last-activity": "Senaste aktivitet", + "voting": "Röstning", + "archived": "Arkiverad", + "delete-linked-card-before-this-card": "Du kan inte radera det här kortet innan du raderat länkat kort som har", + "delete-linked-cards-before-this-list": "Du kan inte radera den här listan innan du raderat länkade kort som pekar till kort i den här listan", + "hide-checked-items": "Dölj markerade objekt", + "task": "Uppgift", + "create-task": "Skapa uppgift", + "ok": "OK", + "organizations": "Organisationer", + "teams": "Grupper", + "displayName": "Visningsnamn", + "shortName": "kort namn", + "website": "Webbsida", + "person": "Person", + "my-cards": "Mina Kort", + "card": "Kort", + "board": "Tavla", + "context-separator": "/", + "myCardsSortChange-title": "Mina Tavlor sortering", + "myCardsSortChangePopup-title": "Mina Tavlor sortering", + "myCardsSortChange-choice-board": "På tavla", + "myCardsSortChange-choice-dueat": "På förfallodatum", + "dueCards-title": "Förfallna tavlor", + "dueCardsViewChange-title": "Förfallna tavlor vy", + "dueCardsViewChangePopup-title": "Förfallna tavlor vy", + "dueCardsViewChange-choice-me": "Jag", + "dueCardsViewChange-choice-all": "Alla användare", + "dueCardsViewChange-choice-all-description": "Visar alla oklara kort med *förfallo* datum från tavlor som användaren har tillgång till.", + "broken-cards": "Trasiga kort", + "board-title-not-found": "Tavla '%s' hittades inte.", + "swimlane-title-not-found": "Simbana '%s' hittades inte.", + "list-title-not-found": "Lista '%s' hittades inte.", + "label-not-found": "Märkning '%s' hittades inte.", + "label-color-not-found": "Märnkingsfärg '%s' hittades inte.", + "user-username-not-found": "Användarnamn '%s' hittades inte.", + "comment-not-found": "Kort med kommentarer innehållande text '%s' hittades inte.", + "globalSearch-title": "Sök Alla Tavlor", + "no-cards-found": "Inga Kort Hittades", + "one-card-found": "Ett Kort hittades", + "n-cards-found": "%s Kort hittade", + "n-n-of-n-cards-found": "__start__-__slut__ av __totala__ Kort Hittade", + "operator-board": "tavla", + "operator-board-abbrev": "b", + "operator-swimlane": "Simbana", + "operator-swimlane-abbrev": "s", + "operator-list": "lista", + "operator-list-abbrev": "l", + "operator-label": "etikett", + "operator-label-abbrev": "#", + "operator-user": "användare", + "operator-user-abbrev": "@", + "operator-member": "medlem", + "operator-member-abbrev": "m", + "operator-assignee": "Tilldelad", + "operator-assignee-abbrev": "a", + "operator-creator": "skapare", + "operator-status": "status", + "operator-due": "förfallotid", + "operator-created": "skapad", + "operator-modified": "ändrad", + "operator-sort": "sortera", + "operator-comment": "kommentar", + "operator-has": "har", + "operator-limit": "gräns", + "predicate-archived": "arkiverad", + "predicate-open": "öppen", + "predicate-ended": "avslutad", + "predicate-all": "alla", + "predicate-overdue": "förfallen", + "predicate-week": "vecka", + "predicate-month": "månad", + "predicate-quarter": "kvartal", + "predicate-year": "år", + "predicate-due": "förfallotid", + "predicate-modified": "ändrad", + "predicate-created": "skapad", + "predicate-attachment": "bilaga", + "predicate-description": "beskrivning", + "predicate-checklist": "checklista", + "predicate-start": "start", + "predicate-end": "slut", + "predicate-assignee": "Tilldelad", + "predicate-member": "medlem", + "predicate-public": "publik", + "predicate-private": "privat", + "operator-unknown-error": "%s är inte en operator", + "operator-number-expected": "operator __operator__ förväntade ett nummer, fick '__värde__'", + "operator-sort-invalid": "Typ av '%s' är ogiltig", + "operator-status-invalid": "'%s' är inte en giltig status", + "operator-has-invalid": "%s är inte en giltig existenskontroll.", + "operator-limit-invalid": "%s är inte en giltig gräns. Gräns ska vara en positiv integer.", + "next-page": "Nästa Sida", + "previous-page": "Föregående Sida", + "heading-notes": "Noteringar", + "globalSearch-instructions-heading": "Sökinstruktioner", + "globalSearch-instructions-description": "Sökningar kan inkludera operatorer för att förfina sökningen. Operatorer är specifierade genon att skriva operator namn och värd separerat med ett kolon. Till exempel, en operator specifikation av 'list:Blocked' skulle limitera sökningen till kort som är innuti en lista döpt till *Blocked*. Om värdet innehåller blanksteg eller specialtecken måste den vara innesluten med citations tecken. (t e x. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Tillgängliga operatorer:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - kort i tavlor matchande den specifierade *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - kort i listor matchande den specifierade *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - kort i simbanor matchande den specifierade *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - kort med kommentar innehållandes *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` -kort med märkning som matchar *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - förkortning för `__operator_label__:<color>` eller `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - kort där*<username>* är en *medlem* eller *tilldelad*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - förkortning för `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - kort där *<username>* är en *medlem*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - kort där *<username>* är en *tilldelad*", + "globalSearch-instructions-operator-creator": "`__operator_member__:<username>` - kort där *<username>* är kortets skapare", + "globalSearch-instructions-operator-due": "`__operator_due__:1` - cards which are due up to *2* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - kort som skapade för *<n>* dagar sedan eller mindre.", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - kort som är modifierade för *<n>* dagar sedan eller mindre", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - där*<status>* är en av följande:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - arkiverade kort", + "globalSearch-instructions-status-all": "`__predicate_all__` - alla arkiverade och oarkiverade kort.", + "globalSearch-instructions-status-ended": "`__predicate_ended__` -kort med ett slutdatum", + "globalSearch-instructions-status-public": "`__predicate_public__` - kort endast i publika tavlor", + "globalSearch-instructions-status-private": "`__predicate_private__` - kort endast i privata tavlor", + "globalSearch-instructions-operator-has": "`__operator_has__:<field> - där*<field>* är en av `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` eller`__predicate_member__`. placer en `-` framför *<field>* söker för frånvaron av ett värde i det fältet (t ex. `has:-due` söker efter kort utan förfallodatum).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - där*<sort-name>* är en av `__predicate_due__`, `__predicate_created__` eller `__predicate_modified__`. för enm fallande sortering, placera en `-` framför sorteringsnamnet.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - där *<n>* är en positivc integer utryckandes antalet kort som ska visas per sida.", + "globalSearch-instructions-notes-1": "Multipla operatörer kan specifieras.", + "globalSearch-instructions-notes-2": "Liknande operatörer *OR*ade tillsammans. Kort som matchar någon av villkoren kommer returneras.\n`__operator_list__:Tillgängliga __operator_list__:Blockerad` skuille returna kort i vilken list som helst som heter *Blockerad* eller *Tillgängliga*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Textsökningar är skiftlägeskänsliga.", + "globalSearch-instructions-notes-5": "Som standard söks inte arkiverade kort.", + "link-to-search": "Länka till denna sökningen", + "excel-font": "Arial", + "number": "Nummer", + "label-colors": "Märkningsfärger", + "label-names": "Märkningsnamn", + "archived-at": "arkiverad den", + "sort-cards": "Sortera Kort", + "cardsSortPopup-title": "Sortera Kort", + "due-date": "Förfallodatum", + "server-error": "Serverfel", + "server-error-troubleshooting": "Skicka in felet som inträffade på servern.\nFör snap-installationer, kör: `sudo snap logs wekan.wekan`\nFör Docker-installationer, kör: `sudo docker logs wekan-app`", + "title-alphabetically": "Titel (Alfabetisk)", + "created-at-newest-first": "Skapad den (Nyast först)", + "created-at-oldest-first": "Skapad den (äldst först)", + "links-heading": "Länkar", + "hide-system-messages-of-all-users": "Göm systemmeddelanden för alla användare", + "now-system-messages-of-all-users-are-hidden": "Systemmeddelande dolda för alla användare", + "move-swimlane": "Flytta simbana", + "moveSwimlanePopup-title": "Flytta simbana", + "custom-field-stringtemplate": "Textmall", + "custom-field-stringtemplate-format": "Formatera (använd %{value} som platshållare)", + "custom-field-stringtemplate-separator": "Avskiljare (använd eller   för mellanslag)", + "custom-field-stringtemplate-item-placeholder": "Tryck enter för att lägga till fler", + "creator": "Skapare", + "filesReportTitle": "Filrapport", + "orphanedFilesReportTitle": "Föräldralös filrapport", + "reports": "Rapporter", + "rulesReportTitle": "Regelrapport", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/sw.i18n.json b/i18n/sw.i18n.json index 959178251..294229770 100644 --- a/i18n/sw.i18n.json +++ b/i18n/sw.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Rudi", "board-change-color": "Badilisha rangi", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Badilisha tarehe", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Funga", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "Nyeusi", "color-blue": "Samawati", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/ta.i18n.json b/i18n/ta.i18n.json index a24ffb532..1c4882950 100644 --- a/i18n/ta.i18n.json +++ b/i18n/ta.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "இணைப்பு ", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "இணைப்புகள் ", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "பின்செல் ", "board-change-color": "நிறம் மாற்று ", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "நாள்கட்டி ", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "நாள் ", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "முழு பெயர் ", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "தொடக்கம் ", "import": "பதிவேற்றம் ", + "impersonate-user": "Impersonate user", "link": "இணை ", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "Trello ல் இருந்து ", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "நிறத்தை மாற்று ", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "மேலும் ", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "தேடு ", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "எண் ", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/th.i18n.json b/i18n/th.i18n.json index 25b98faee..e57a8dc52 100644 --- a/i18n/th.i18n.json +++ b/i18n/th.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "เอกสารที่เก็บไว้", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "กำหนดสมาชิก", "attached": "แนบมาด้วย", "attachment": "สิ่งที่แนบมา", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "ลบสิ่งที่แนบมาหรือไม่", "attachments": "สิ่งที่แนบมา", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "ย้อนกลับ", "board-change-color": "เปลี่ยนสี", "board-nb-stars": "ติดดาว %s", "board-not-found": "ไม่มีบอร์ด", "board-private-info": "บอร์ดนี้จะเป็น <strong>ส่วนตัว</strong>.", "board-public-info": "บอร์ดนี้จะเป็น <strong>สาธารณะ</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "เปลี่ยนสีพื้นหลังบอร์ด", "boardChangeTitlePopup-title": "เปลี่ยนชื่อบอร์ด", "boardChangeVisibilityPopup-title": "เปลี่ยนการเข้าถึง", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "รายการ", "bucket-example": "ตัวอย่างเช่น “ระบบที่ต้องทำ”", "cancel": "ยกเลิก", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "แนบจาก", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "ลบการ์ดนี้หรือไม่", "cardDetailsActionsPopup-title": "การดำเนินการการ์ด", "cardLabelsPopup-title": "ป้ายกำกับ", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "การ์ด", "cards-count": "การ์ด", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "ปิด", "close-board": "ปิดบอร์ด", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "ดำ", "color-blue": "น้ำเงิน", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "ปัจจุบัน", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "วันที่", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "คุณต้องเป็นสมาชิกของบอร์ดนี้ถึงจะทำได้", "error-json-malformed": "ข้อความของคุณไม่ใช่ JSON", "error-json-schema": "รูปแบบข้้้อมูล JSON ของคุณไม่ถูกต้อง", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "รายการนี้ไม่มีอยู่", "error-user-doesNotExist": "ผู้ใช้นี้ไม่มีอยู่", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "ผู้ใช้รายนี้ไม่ได้สร้าง", "error-username-taken": "ชื่อนี้ถูกใช้งานแล้ว", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "ส่งออกกระดาน", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "ส่งออกกระดาน", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "กรอง", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "ล้างตัวกรอง", + "filter-labels-label": "Filter by label", "filter-no-label": "ไม่มีฉลาก", + "filter-member-label": "Filter by member", "filter-no-member": "ไม่มีสมาชิก", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "กรองบน", "filter-on-desc": "คุณกำลังกรองการ์ดในบอร์ดนี้ คลิกที่นี่เพื่อแก้ไขตัวกรอง", "filter-to-selection": "กรองตัวเลือก", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "ชื่อ นามสกุล", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "สร้างบอร์ด", "home": "หน้าหลัก", "import": "นำเข้า", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "นำเข้าบอร์ดจาก Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "ใน Trello ของคุณให้ไปที่ 'Menu' และไปที่ More -> Print and Export -> Export JSON และคัดลอกข้อความจากนั้น", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "วางข้อมูล JSON ที่ถูกต้องของคุณที่นี่", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "แผนที่สมาชิก", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review การทำแผนที่สมาชิก", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "เลือกการ์ดทั้งหมดในรายการนี้", "set-color-list": "Set Color", "listActionPopup-title": "รายการการดำเนิน", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "นำเข้าการ์ด Trello", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "เพิ่มเติม", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "ย้ายไปบน", "moveSelectionPopup-title": "เลือกย้าย", "multi-selection": "เลือกหลายรายการ", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "เลือกหลายรายการเมื่อ", "muted": "ไม่ออกเสียง", "muted-info": "คุณจะไม่ได้รับแจ้งการเปลี่ยนแปลงใด ๆ ในบอร์ดนี้", @@ -441,8 +525,9 @@ "search": "ค้นหา", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "กำหนดตัวเองให้การ์ดนี้", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "กรองการ์ดฉัน", "shortcut-show-shortcuts": "นำรายการทางลัดนี้ขึ้น", "shortcut-toggle-filterbar": "สลับแถบกรองสไลด์ด้้านข้าง", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "สลับโชว์แถบด้านข้าง", "show-cards-minimum-count": "แสดงจำนวนของการ์ดถ้ารายการมีมากกว่า(เลื่อนกำหนดตัวเลข)", "sidebar-open": "เปิดแถบเลื่อน", @@ -481,7 +567,15 @@ "upload": "อัพโหลด", "upload-avatar": "อัพโหลดรูปภาพ", "uploaded-avatar": "ภาพอัพโหลดแล้ว", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "ชื่อผู้ใช้งาน", + "import-usernames": "Import Usernames", "view-it": "ดู", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "เฝ้าดู", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/tr.i18n.json b/i18n/tr.i18n.json index 9626a764b..46ae0b182 100644 --- a/i18n/tr.i18n.json +++ b/i18n/tr.i18n.json @@ -23,9 +23,9 @@ "act-createSwimlane": "__board__ panosuna __swimlane__ kulvarı oluşturuldu", "act-createCard": "__board__ panosunun __swimlane__ kulvarının __list__ listesinin __card__ kartı oluşturuldu", "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", + "act-deleteCustomField": "__customField__ özel alanı __board__ board'dan silindi.", "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", + "act-createList": "__list__ listesi __board__ board'una eklenildi.", "act-addBoardMember": "__board__ panosuna __member__ kullanıcısı eklendi", "act-archivedBoard": "__board__ panosu Arşiv'e taşındı", "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", @@ -74,10 +74,17 @@ "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "%s yorum düzenlendi", - "activity-deleteComment": "deleted comment %s", + "activity-deleteComment": "%s yorum silindi", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Ek Ekle", "add-board": "Pano Ekle", + "add-template": "Add Template", "add-card": "Kart Ekle", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Kulvar Ekle", "add-subtask": "Alt Görev Ekle", "add-checklist": "Yapılacak Listesi Ekle", @@ -113,6 +120,8 @@ "archives": "Arşivle", "template": "Şablon", "templates": "Şablonlar", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Üye ata", "attached": "dosya(sı) eklendi", "attachment": "Ek Dosya", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Ek Silinsin mi?", "attachments": "Ekler", "auto-watch": "Oluşan yeni panoları kendiliğinden izlemeye al", - "avatar-too-big": "Avatar boyutu çok büyük (En fazla 70KB olabilir)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Geri", "board-change-color": "Renk değiştir", "board-nb-stars": "%s yıldız", "board-not-found": "Pano bulunamadı", "board-private-info": "Bu pano <strong>gizli</strong> olacak.", "board-public-info": "Bu pano <strong>genel</strong>e açılacaktır.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Pano arkaplan rengini değiştir", "boardChangeTitlePopup-title": "Panonun Adını Değiştir", "boardChangeVisibilityPopup-title": "Görünebilirliği Değiştir", @@ -137,7 +147,8 @@ "board-view": "Pano Görünümü", "board-view-cal": "Takvim", "board-view-swimlanes": "Kulvarlar", - "board-view-collapse": "Collapse", + "board-view-collapse": "Katla", + "board-view-gantt": "Gant Şeması", "board-view-lists": "Listeler", "bucket-example": "Örn: \"Marketten Alacaklarım\"", "cancel": "İptal", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Eklenme", "cardCustomField-datePopup-title": "Tarihi değiştir", "cardCustomFieldsPopup-title": "Özel alanları düzenle", + "cardStartVotingPopup-title": "Seçim başlat", + "positiveVoteMembersPopup-title": "Kabul oyu verenler", + "negativeVoteMembersPopup-title": "Red oyu verenler", + "card-edit-voting": "Seçimi düzenle", + "editVoteEndDatePopup-title": "Seçimin bitiş tarihini değiştir", + "allowNonBoardMembers": "Tüm giriş yapanlara aç", + "vote-question": "Seçim sorusu", + "vote-public": "Kimin nasıl oy verdiğini göster", + "vote-for-it": "kabul", + "vote-against": "red", + "deleteVotePopup-title": "Seçim silinsin mi?", + "vote-delete-pop": "Silme kalıcı bir işlemdir. Bu seçimle ilgili tüm eylemleri yitireceksiniz.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Kart Silinsin mi?", "cardDetailsActionsPopup-title": "Kart işlemleri", "cardLabelsPopup-title": "Etiketler", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Şablon oluştur", "cards": "Kartlar", "cards-count": "Kartlar", + "cards-count-one": "Kart", "casSignIn": "CAS ile giriş yapın", "cardType-card": "Kart", "cardType-linkedCard": "Bağlantılı kart", @@ -191,9 +236,10 @@ "close": "Kapat", "close-board": "Panoyu kapat", "close-board-pop": "\n92/5000\nAna başlıktaki “Arşiv” düğmesine tıklayarak tahtayı geri yükleyebilirsiniz.", + "close-card": "Close Card", "color-black": "siyah", "color-blue": "mavi", - "color-crimson": "crimson", + "color-crimson": "kızıl", "color-darkgreen": "koyu yeşil", "color-gold": "altın rengi", "color-gray": "gri", @@ -201,30 +247,30 @@ "color-indigo": "çivit", "color-lime": "misket limonu", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-mistyrose": "sisli", + "color-navy": "koyu lacivert", "color-orange": "turuncu", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "soluk turkuvaz", + "color-peachpuff": "şeftali", "color-pink": "pembe", - "color-plum": "plum", + "color-plum": "erik moru", "color-purple": "mor", "color-red": "kırmızı", - "color-saddlebrown": "saddlebrown", + "color-saddlebrown": "eyer kahverengi", "color-silver": "gümüş rengi", "color-sky": "açık mavi", - "color-slateblue": "slateblue", + "color-slateblue": "kurşun mavisi", "color-white": "beyaz", "color-yellow": "sarı", - "unset-color": "Unset", + "unset-color": "Seçilmemiş", "comment": "Yorum", "comment-placeholder": "Yorum Yaz", "comment-only": "Sadece yorum", "comment-only-desc": "Sadece kartlara yorum yazabilir.", "no-comments": "Yorum Yok", "no-comments-desc": "Yorumlar ve aktiviteleri göremiyorum.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", + "worker": "Çalışan", + "worker-desc": "Yalnızca kartları taşıyabilir, kendisini karta atayabilir ve yorum yapabilir.", "computer": "Bilgisayar", "confirm-subtask-delete-dialog": "Alt görevi silmek istediğinizden emin misiniz?", "confirm-checklist-delete-dialog": "Kontrol listesini silmek istediğinden emin misin?", @@ -244,6 +290,8 @@ "current": "mevcut", "custom-field-delete-pop": "Bunun geri dönüşü yoktur. Bu özel alan tüm kartlardan kaldırılıp tarihçesi yokedilecektir.", "custom-field-checkbox": "İşaret kutusu", + "custom-field-currency": "Para birimi", + "custom-field-currency-option": "Para birimi kodu", "custom-field-date": "Tarih", "custom-field-dropdown": "Açılır liste", "custom-field-dropdown-none": "(hiçbiri)", @@ -297,34 +345,60 @@ "error-board-notAMember": "Bu işlemi yapmak için panoya üye olmalısın.", "error-json-malformed": "Girilen metin geçerli bir JSON formatında değil", "error-json-schema": "Girdiğin JSON metni tüm bilgileri doğru biçimde barındırmıyor", + "error-csv-schema": "CSV'niz (Virgülle Ayrılmış Değerler) / TSV'niz (Sekmeyle Ayrılmış Değerler) doğru formatta uygun bilgileri içermiyor", "error-list-doesNotExist": "Liste bulunamadı", "error-user-doesNotExist": "Kullanıcı bulunamadı", "error-user-notAllowSelf": "Kendi kendini davet edemezsin", "error-user-notCreated": "Bu üye oluşturulmadı", "error-username-taken": "Kullanıcı adı zaten alınmış", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Bu e-posta adresi daha önceden alınmış", "export-board": "Panoyu dışarı aktar", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", + "export-board-json": "Panoyu JSON olarak dışarı aktar", + "export-board-csv": "Panoyu CSV olarak dışarı aktar", + "export-board-tsv": "Panoyu TSV olarak dışarı aktar", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Panoyu HTML olarak dışarı aktar", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Panoyu dışarı aktar", + "exportCardPopup-title": "Export card", + "sort": "Sırala", + "sort-desc": "Listeyi Sıralamak için Tıklayın", + "list-sort-by": "Listeyi Sırala:", "list-label-modifiedAt": "Son Erişim Zamanı...", "list-label-title": "Listenin Adı...", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", + "list-label-sort": "Manuel Siparişiniz", + "list-label-short-modifiedAt": "(S)", + "list-label-short-title": "(A)", "list-label-short-sort": "(M)", "filter": "Filtre", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", + "filter-cards": "Kartları veya Listeleri Filtrele", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", + "list-filter-label": "Listeyi Başlığa Göre Filtrele", "filter-clear": "Filtreyi temizle", + "filter-labels-label": "Etikete göre filtrele", "filter-no-label": "Etiket yok", - "filter-no-member": "Üye yok", + "filter-member-label": "Üyeye göre filtrele", + "filter-no-member": "Üyesi olmayan", + "filter-assignee-label": "Atanana göre filtrele", + "filter-no-assignee": "Atanmayan", + "filter-custom-fields-label": "Özel alanlara göre filtrele", "filter-no-custom-fields": "Hiç özel alan yok", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", + "filter-show-archive": "Arşivlenmiş listeleri göster", + "filter-hide-empty": "Boş listeleri gizle", "filter-on": "Filtre aktif", "filter-on-desc": "Bu panodaki kartları filtreliyorsunuz. Fitreyi düzenlemek için tıklayın.", "filter-to-selection": "Seçime göre filtreleme yap", + "other-filters-label": "Diğer Filtreler", "advanced-filter-label": "Gelişmiş Filtreleme", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Ad Soyad", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Pano Oluşturma", "home": "Ana Sayfa", "import": "İçeri aktar", + "impersonate-user": "Kullanıcının kimliğine bürün", "link": "Bağlantı", "import-board": "panoyu içe aktar", "import-board-c": "Panoyu içe aktar", "import-board-title-trello": "Trello'dan panoyu içeri aktar", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "İçe aktarılan pano şu anki panonun verilerinin üzerine yazılacak ve var olan veriler silinecek.", + "import-board-title-wekan": "Önceki dışa aktarımdaki panoyu içe aktar", + "import-board-title-csv": "CSV/TSV formatındaki panoyu içeri aktar", "from-trello": "Trello'dan", - "from-wekan": "From previous export", + "from-wekan": "Önceki dışa aktarımdan", + "from-csv": "CSV/TSV'den", "import-board-instruction-trello": "Trello panonuzda 'Menü'ye gidip 'Daha fazlası'na tıklayın, ardından 'Yazdır ve Çıktı Al'ı seçip 'JSON biçiminde çıktı al' diyerek çıkan metni buraya kopyalayın.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", + "import-board-instruction-csv": "Virgülle Ayrılmış Değerlerinizi (CSV) / Sekmeyle Ayrılmış Değerlerinizi (TSV) yapıştırın.", + "import-board-instruction-wekan": "Panonuzda \"Menü\" ye, ardından \"Panoyu dışa aktar\" a gidin ve indirilen dosyadaki metni kopyalayın.", + "import-board-instruction-about-errors": "Panoyu içe aktarırken hata alıyorsanız, içe alma hala devam ediyor olabilir ya da Tüm Panolar sayfasında yer alıyor olabilir.", "import-json-placeholder": "Geçerli JSON verisini buraya yapıştırın", + "import-csv-placeholder": "Geçerli CSV / TSV verilerinizi buraya yapıştırın", "import-map-members": "Üyeleri eşleştirme", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map": "İçe aktarılan panonuzda bazı üyeler var. Lütfen kullanıcılarınıza aktarmak istediğiniz üyeleri eşleyin", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Üye eşleştirmesini kontrol et", "import-user-select": "Bu üye olarak kullanmak istediğiniz mevcut kullanıcınızı seçin", "importMapMembersAddPopup-title": "Üye seç", @@ -370,18 +448,22 @@ "leaveBoardPopup-title": "Panodan ayrılmak istediğinize emin misiniz?", "link-card": "Bu kartın bağlantısı", "list-archive-cards": "Bu listedeki tüm kartları arşive taşı", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", + "list-archive-cards-pop": "Bu eylem bu listedeki tüm kartları panodan kaldıracaktır. Arşivdeki kartları görüntülemek ve panoya geri getirmek için \"Menü\"> \"Arşiv\"i tıklayın.", "list-move-cards": "Listedeki tüm kartları taşı", "list-select-cards": "Listedeki tüm kartları seç", "set-color-list": "Rengi Ayarla", "listActionPopup-title": "Liste İşlemleri", + "settingsUserPopup-title": "Kullanıcı Ayarları", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Kulvar İşlemleri", - "swimlaneAddPopup-title": "Add a Swimlane below", + "swimlaneAddPopup-title": "Aşağı kulvar ekle", "listImportCardPopup-title": "Bir Trello kartını içeri aktar", - "listMorePopup-title": "Daha", + "listImportCardsTsvPopup-title": "İçe aktar Excel CSV/TSV", + "listMorePopup-title": "Daha fazla", "link-list": "Listeye doğrudan bağlantı", "list-delete-pop": "Etkinlik akışınızdaki tüm eylemler geri kurtarılamaz şekilde kaldırılacak. Bu işlem geri alınamaz.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", + "list-delete-suggest-archive": "Bir listeyi panodan kaldırmak için Arşive taşıyabilir ve kayıtları saklayabilirsiniz.", "lists": "Listeler", "swimlanes": "Kulvarlar", "log-out": "Oturum Kapat", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Yukarı taşı", "moveSelectionPopup-title": "Seçimi taşı", "multi-selection": "Çoklu seçim", + "multi-selection-label": "Seçim için etiket belirle", + "multi-selection-member": "Seçim için üye belirle", "multi-selection-on": "Çoklu seçim açık", "muted": "Sessiz", "muted-info": "Bu panodaki hiçbir değişiklik hakkında bildirim almayacaksınız", @@ -413,7 +497,7 @@ "optional": "isteğe bağlı", "or": "veya", "page-maybe-private": "Bu sayfa gizli olabilir. <a href='%s'>Oturum açarak</a> görmeyi deneyin.", - "page-not-found": "Sayda bulunamadı.", + "page-not-found": "Sayfa bulunamadı.", "password": "Parola", "paste-or-dragdrop": "Dosya eklemek için yapıştırabilir, veya (eğer resimse) sürükle bırak yapabilirsiniz", "participating": "Katılımcılar", @@ -424,8 +508,8 @@ "private-desc": "Bu pano gizli. Sadece panoya ekli kişiler görüntüleyebilir ve düzenleyebilir.", "profile": "Kullanıcı Sayfası", "public": "Genel", - "public-desc": "Bu pano genel. Bağlantı adresi ile herhangi bir kimseye görünür ve Google gibi arama motorlarında gösterilecektir. Panoyu, sadece eklenen kişiler düzenleyebilir.", - "quick-access-description": "Bu bara kısayol olarak bir pano eklemek için panoyu yıldızlamalısınız", + "public-desc": "Bu pano genel bir panodur. Bağlantıya sahip olan herkes panoyu görüntüleyebilir, ayrıca panonuz Google gibi arama motorlarında görünür. Panoyu, sadece panoya eklenen kişiler düzenleyebilir.", + "quick-access-description": "Yıldızladığınız panolar burada gözükür", "remove-cover": "Kapak Resmini Kaldır", "remove-from-board": "Panodan Kaldır", "remove-label": "Etiketi Kaldır", @@ -440,9 +524,10 @@ "save": "Kaydet", "search": "Arama", "rules": "Kurallar", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Aranılacak metin?", + "search-cards": "Bu panodaki kart / liste başlıkları, açıklamalar ve özel alanlarda arama yapın", + "search-example": "Aradığınız metni yazın ve Enter tuşuna basın", "select-color": "Renk Seç", + "select-board": "Panoyu Seçin", "set-wip-limit-value": "Bu listedeki en fazla öğe sayısı için bir sınır belirleyin", "setWipLimitPopup-title": "Devam Eden İş Sınırı Belirle", "shortcut-assign-self": "Kendini karta ata", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Kartlarımı filtrele", "shortcut-show-shortcuts": "Kısayollar listesini getir", "shortcut-toggle-filterbar": "Filtre kenar çubuğunu aç/kapa", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Pano kenar çubuğunu aç/kapa", "show-cards-minimum-count": "Eğer listede şu sayıdan fazla öğe varsa kart sayısını göster: ", "sidebar-open": "Kenar Çubuğunu Aç", @@ -481,7 +567,15 @@ "upload": "Yükle", "upload-avatar": "Avatar yükle", "uploaded-avatar": "Avatar yüklendi", + "custom-top-left-corner-logo-image-url": "Özel Sol Üst Köşe Logo Resmi URL'si", + "custom-top-left-corner-logo-link-url": "Özel Sol Üst Köşe Logo Bağlantı URL'si", + "custom-top-left-corner-logo-height": "Özel Üst Sol Köşe Logo Yüksekliği. Varsayılan: 27", + "custom-login-logo-image-url": "Özel Oturum Açma Logosu Resmi URL'si", + "custom-login-logo-link-url": "Özel Oturum Açma Logosu Bağlantı URL'si", + "text-below-custom-login-logo": "Özel Oturum Açma Logosunun altındaki metin", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Kullanıcı adı", + "import-usernames": "Import Usernames", "view-it": "Görüntüle", "warn-list-archived": "Uyarı: Bu kart arşivdeki bir listede", "watch": "Takip Et", @@ -491,9 +585,9 @@ "welcome-swimlane": "Kilometre taşı", "welcome-list1": "Temel", "welcome-list2": "Gelişmiş", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", + "card-templates-swimlane": "Kart şablonları", + "list-templates-swimlane": "Liste şablonları", + "board-templates-swimlane": "Pano şablonları", "what-to-do": "Ne yapmak istiyorsunuz?", "wipLimitErrorPopup-title": "Geçersiz Devam Eden İş Sınırı", "wipLimitErrorPopup-dialog-pt1": "Bu listedeki iş sayısı belirlediğiniz sınırdan daha fazla.", @@ -524,21 +618,21 @@ "email-smtp-test-text": "E-Posta başarıyla gönderildi", "error-invitation-code-not-exist": "Davetiye kodu bulunamadı", "error-notAuthorized": "Bu sayfayı görmek için yetkiniz yok.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", + "webhook-title": "Bağlantı Adı", + "webhook-token": "Belirteç (Kimlik Doğrulama için İsteğe Bağlı)", "outgoing-webhooks": "Dışarı giden bağlantılar", - "bidirectional-webhooks": "Two-Way Webhooks", + "bidirectional-webhooks": "İki yönlü bağlantılar", "outgoingWebhooksPopup-title": "Dışarı giden bağlantılar", "boardCardTitlePopup-title": "Kart Başlığı Filtresi", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", + "disable-webhook": "Bu bağlantıyı devre dışı bırak", + "global-webhook": "Global Bağlantılar", "new-outgoing-webhook": "Yeni Dışarı Giden Web Bağlantısı", "no-name": "(Bilinmeyen)", "Node_version": "Node sürümü", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", + "Meteor_version": "Meteor sürümü", + "MongoDB_version": "MongoDB sürümü", + "MongoDB_storage_engine": "MongoDB depolama motoru", + "MongoDB_Oplog_enabled": "MongoDB Oplog etkin", "OS_Arch": "İşletim Sistemi Mimarisi", "OS_Cpus": "İşletim Sistemi İşlemci Sayısı", "OS_Freemem": "İşletim Sistemi Kullanılmayan Bellek", @@ -553,7 +647,8 @@ "minutes": "dakika", "seconds": "saniye", "show-field-on-card": "Bu alanı kartta göster", - "automatically-field-on-card": "Tüm kartlara otomatik alan oluştur", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Minikard üzerindeki alan etiketini göster", "yes": "Evet", "no": "Hayır", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "E-posta Değiştirmeye İzin Ver", "accounts-allowUserNameChange": "Kullanıcı adı değiştirmeye izin ver", "createdAt": "Oluşturulma tarihi", + "modifiedAt": "Modified at", "verified": "Doğrulanmış", "active": "Aktif", "card-received": "Giriş", @@ -575,22 +671,23 @@ "setListColorPopup-title": "Renk seçimi yap", "assigned-by": "Atamayı yapan", "requested-by": "Talep Eden", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Silme kalıcıdır. Bu kartla ilişkili tüm listeleri, kartları ve işlemleri kaybedeceksiniz.", "delete-board-confirm-popup": "Tüm listeler, kartlar, etiketler ve etkinlikler silinecek ve pano içeriğini kurtaramayacaksınız. Geri dönüş yok.", "boardDeletePopup-title": "Panoyu Sil?", "delete-board": "Panoyu Sil", - "default-subtasks-board": "Subtasks for __board__ board", + "default-subtasks-board": "__board__ panosu için alt görevler", "default": "Varsayılan", "queue": "Sıra", "subtask-settings": "Alt Görev ayarları", - "card-settings": "Card Settings", + "card-settings": "Kart ayarları", "boardSubtaskSettingsPopup-title": "Pano alt görev ayarları", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", + "boardCardSettingsPopup-title": "Kart ayarları", + "deposit-subtasks-board": "Alt görevleri bu panoda sakla:", "deposit-subtasks-list": "Alt görevlerin açılacağı liste:", "show-parent-in-minicard": "Mini kart içinde üst kartı göster", "prefix-with-full-path": "Tam yolunu önüne ekle", - "prefix-with-parent": "Prefix with parent", + "prefix-with-parent": "Ana görev ile ön adlandır", "subtext-with-full-path": "Tam yolu ile alt metin", "subtext-with-parent": "üst öge ile alt metin", "change-card-parent": "Kartın üst kartını değiştir", @@ -614,16 +711,19 @@ "r-delete-rule": "Kuralı sil", "r-new-rule-name": "Yeni kural başlığı", "r-no-rules": "Kural yok", + "r-trigger": "Tetikleyici", + "r-action": "Aksiyon", "r-when-a-card": "Kart eklendiğinde", "r-is": "is", "r-is-moved": "taşındı", - "r-added-to": "eklendi", - "r-removed-from": "Removed from", + "r-added-to": "Eklendi:", + "r-removed-from": "Çıkarıldı:", "r-the-board": "pano", "r-list": "liste", + "list": "Liste", "set-filter": "Filtrele", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", + "r-moved-to": "Şuraya taşındı:", + "r-moved-from": "Şuradan taşındı:", "r-archived": "Arşive taşındı", "r-unarchived": "Arşivden geri çıkarıldı", "r-a-card": "Kart", @@ -633,13 +733,13 @@ "r-when-a-member": "When a member is", "r-when-the-member": "When the member", "r-name": "isim", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", + "r-when-a-attach": "Bir ek olduğunda", + "r-when-a-checklist": "Bir kontrol listesi şu olduğunda:", + "r-when-the-checklist": "Bir kontrol listesine şu olduğunda:", "r-completed": "Tamamlandı", "r-made-incomplete": "Tamamlanmamış", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", + "r-when-a-item": "Bir kontrol listesi öğesi şu olduğunda:", + "r-when-the-item": "Bir kontrol listesi öğesine şu olduğunda:", "r-checked": "İşaretlendi", "r-unchecked": "İşaret Kaldırıldı", "r-move-card-to": "Kartı taşı", @@ -654,7 +754,7 @@ "r-label": "etiket", "r-member": "üye", "r-remove-all": "Tüm üyeleri karttan çıkarın", - "r-set-color": "Set color to", + "r-set-color": "Rengi şu değere ayarla:", "r-checklist": "Kontrol Listesi", "r-check-all": "Tümünü işaretle", "r-uncheck-all": "Tüm işaretleri kaldır", @@ -662,9 +762,10 @@ "r-check": "işaretle", "r-uncheck": "İşareti Kaldır", "r-item": "öge", - "r-of-checklist": "of checklist", + "r-of-checklist": "kontrol listesi", "r-send-email": "E-Posta Gönder", "r-to": "to", + "r-of": "of", "r-subject": "Konu", "r-rule-details": "Kural Detayları", "r-d-move-to-top-gen": "Kartı listesinin en üstüne taşı", @@ -685,11 +786,11 @@ "r-d-add-member": "Üye Ekle", "r-d-remove-member": "Üye Sil", "r-d-remove-all-member": "Tüm Üyeleri Sil", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", + "r-d-check-all": "Listenin tüm öğelerini kontrol et", + "r-d-uncheck-all": "Listedeki tüm öğelerin işaretini kaldır", "r-d-check-one": "Ögeyi kontrol et", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", + "r-d-uncheck-one": "Öğenin işaretini kaldır", + "r-d-check-of-list": "kontrol listesi", "r-d-add-checklist": "Kontrol listesine ekle", "r-d-remove-checklist": "Kontrol listesini kaldır", "r-by": "tarafından", @@ -701,15 +802,15 @@ "r-board-note": "Not: Her olası değere uyması için bir alanı boş bırakın.", "r-checklist-note": "Not: kontrol listesindeki öğelerin virgülle ayrılmış değerler olarak yazılması gerekir.", "r-when-a-card-is-moved": "Bir kart başka bir listeye taşındığında", - "r-set": "Set", + "r-set": "Ayarla", "r-update": "Güncelle", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", + "r-datefield": "tarih alanı", + "r-df-start-at": "başlangıç", + "r-df-due-at": "vade", + "r-df-end-at": "bitiş", + "r-df-received-at": "alındı", "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-remove-value-from": "Değeri şuradan kaldır:", "ldap": "LDAP", "oauth2": "Oauth2", "cas": "CAS", @@ -718,52 +819,244 @@ "custom-product-name": "Özel Ürün Adı", "layout": "Düzen", "hide-logo": "Logoyu Gizle", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", + "add-custom-html-after-body-start": "<body>'den sonra Özel HTML Ekle", + "add-custom-html-before-body-end": "</body>'den önce Özel HTML Ekle", "error-undefined": "Bir şeyler yanlış gitti", "error-ldap-login": "Giriş yapmaya çalışırken bir hata oluştu", - "display-authentication-method": "Display Authentication Method", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", + "display-authentication-method": "Kimlik Doğrulama Yöntemini Görüntüle", + "default-authentication-method": "Varsayılan Kimlik Doğrulama Yöntemi", + "duplicate-board": "Panoyu Çoğalt", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", + "people-number": "Kişi sayısı:", "swimlaneDeletePopup-title": "Kulvar silinsin mi?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", + "swimlane-delete-pop": "Tüm eylemler etkinlik akışından kaldırılacak ve kulvarınızı kurtaramayacaksınız. Geri alınamaz.", "restore-all": "Her şeyi eski haline getir", "delete-all": "Hepsini sil", "loading": "Yükleniyor, lütfen bekleyiniz", - "previous_as": "last time was", + "previous_as": "son kez", "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", + "a-dueAt": "son tarih değiştirildi", + "a-endAt": "bitiş zamanı değiştirildi", + "a-startAt": "başlangıç zamanı değiştirildi", + "a-receivedAt": "alınma zamanı değiştirildi", + "almostdue": "%s'in vadesi yaklaşıyor", + "pastdue": "%s'in vadesi geçti", + "duenow": "%s'in vadesi bugün", "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", "act-withDue": "__list__/__card__ due reminders [__board__]", "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "act-atUserComment": "[__board__] __list__/__card__ içinde bahsedildiniz", "delete-user-confirm-popup": "Bu kullanıcı hesabını silmek istediğinize emin misiniz? Bu işlemi geri alamazsınız.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Kullanıcılara hesaplarını silmek için izin ver.", "hide-minicard-label-text": "Mini kart etiklerini gizle", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", + "show-desktop-drag-handles": "Masaüstü sürükleme tutamaçlarını göster", + "assignee": "Atanan", + "cardAssigneesPopup-title": "Atanan", + "addmore-detail": "Daha ayrıntılı bir açıklama ekle", + "show-on-card": "Kartta Göster", + "new": "Yeni", + "editOrgPopup-title": "Organizasyonu Düzenle", + "newOrgPopup-title": "Yeni Organizasyon", + "editTeamPopup-title": "Takımı Düzenle", + "newTeamPopup-title": "Yeni Takım", + "editUserPopup-title": "Kullanıcıyı Düzenle", + "newUserPopup-title": "Yeni Kullanıcı", "notifications": "Bildirim", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "view-all": "Tümünü gör", + "filter-by-unread": "Okunmamışlara göre filtrele", + "mark-all-as-read": "Tümünü okundu olarak işaretle", + "remove-all-read": "Okunanların tümünü kaldır", + "allow-rename": "Yeniden Adlandırmaya İzin Ver", + "allowRenamePopup-title": "Yeniden Adlandırmaya İzin Ver", + "start-day-of-week": "Haftanın başlangıç gününü ayarla", + "monday": "Pazartesi", + "tuesday": "Salı", + "wednesday": "Çarşamba", + "thursday": "Perşembe", + "friday": "Cuma", + "saturday": "Cumartesi", + "sunday": "Pazar", + "status": "Durum", + "swimlane": "Kulvar", + "owner": "Sahibi", + "last-modified-at": "Son değiştirilme", + "last-activity": "Son aktivite", + "voting": "Oylama", + "archived": "Arşivlendi", + "delete-linked-card-before-this-card": "Bu kartı, sahip olan bağlantılı kartı silmeden önce silemezsiniz.", + "delete-linked-cards-before-this-list": "Bu listedeki kartlara işaret eden bağlantılı kartları silmeden önce bu listeyi silemezsiniz.", + "hide-checked-items": "İşaretli öğeleri gizle", + "task": "Görev", + "create-task": "Görev Oluştur", + "ok": "OK", + "organizations": "Organizasyonlar", + "teams": "Takımlar", + "displayName": "Görünen İsim", + "shortName": "Short Name", + "website": "Web Site", + "person": "Kişi", + "my-cards": "My Cards", + "card": "Kart", + "board": "Pano", + "context-separator": "/", + "myCardsSortChange-title": "Kartlarımı Sırala", + "myCardsSortChangePopup-title": "Kartlarımı Sırala", + "myCardsSortChange-choice-board": "Pano Tarafından", + "myCardsSortChange-choice-dueat": "Son Tarihe Göre", + "dueCards-title": "Son Kartlar", + "dueCardsViewChange-title": "Son Kart Görünümü", + "dueCardsViewChangePopup-title": "Son Kart Görünümü", + "dueCardsViewChange-choice-me": "Ben mi", + "dueCardsViewChange-choice-all": "Tüm Kullanıcılar", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "liste", + "operator-list-abbrev": "l", + "operator-label": "etiket", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "üye", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "vade", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "vade", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "Kontrol Listesi", + "predicate-start": "başlangıç", + "predicate-end": "bitiş", + "predicate-assignee": "assignee", + "predicate-member": "üye", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Sayı", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/uk.i18n.json b/i18n/uk.i18n.json index 39b223876..3ecdfe29f 100644 --- a/i18n/uk.i18n.json +++ b/i18n/uk.i18n.json @@ -1,19 +1,19 @@ { "accept": "Прийняти", - "act-activity-notify": "Сповіщення активності", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-activity-notify": "Сповіщення про дії учасників", + "act-addAttachment": "прикріплено вкладення __attachment__ до картки__card__ у списку __list__ на доріжці __swimlane__ дошки__board__", + "act-deleteAttachment": "видалено вкладення __attachment__ з картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-addSubtask": "додано підзадачу __subtask__ для картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-addLabel": "Додано мітку __label__ до картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__\n ", + "act-addedLabel": "Додано мітку __label__ до картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__\n ", + "act-removeLabel": "Знято мітку __label__ з картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-removedLabel": "Знято мітку __label__ з картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-addChecklist": "додано контрольний список __checklist__ до картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-addChecklistItem": "додано пункт __checklistItem__ до контрольного списку __checklist__ у картці __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-removeChecklist": "видалено контрольний список __checklist__ з картки __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-removeChecklistItem": "видалено пункт __checklistItem__ з контрольного списку __checklist__ у картці __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-checkedItem": "позначено виконаним __checklistItem__ у контрольному списку __checklist__ у картці __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", + "act-uncheckedItem": "позначено як невиконане __checklistItem__ у контрольному списку __checklist__ у картці __card__ у списку __list__ на доріжці __swimlane__ дошки __board__", "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "відредаговано коментар %s", "activity-deleteComment": "видалено коментар %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Додати вкладення", "add-board": "Додати дошку", + "add-template": "Add Template", "add-card": "Додати картку", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Додати підзадачу", "add-checklist": "Add Checklist", @@ -90,7 +97,7 @@ "addMemberPopup-title": "Користувачі", "admin": "Адмін", "admin-desc": "Може переглядати і редагувати картки, відаляти учасників та змінювати налаштування для дошки.", - "admin-announcement": "Announcement", + "admin-announcement": "Оголошення", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", "all-boards": "Всі дошки", @@ -98,46 +105,50 @@ "and-n-other-card_plural": "та __count__ інших карток", "apply": "Прийняти", "app-is-offline": "Завантаження, будь ласка, зачекайте. Оновлення сторінки призведе до втрати даних. Якщо завантаження не працює, перевірте, чи не зупинився сервер.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", + "archive": "Перенести до Архіву", + "archive-all": "Перенести Все до Архіву", + "archive-board": "Перенести Дошку до Архіву", + "archive-card": "Перенести Картку до Архіву", + "archive-list": "Перенести Список до Архіву", "archive-swimlane": "Move Swimlane to Archive", "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", + "archiveBoardPopup-title": "Перенести Дошку до Архіву?", "archived-items": "Архів", "archived-boards": "Дошки в архіві", "restore-board": "Відновити дошку", "no-archived-boards": "Немає дошок в архіві", "archives": "Архів", - "template": "Template", - "templates": "Templates", - "assign-member": "Assign member", + "template": "Шаблон", + "templates": "Шаблони", + "template-container": "Template Container", + "add-template-container": "Add Template Container", + "assign-member": "Призначити користувача", "attached": "доданно", "attachment": "Додаток", "attachment-delete-pop": "Видалення Додатку безповоротне. Тут нема відміні (undo).", "attachmentDeletePopup-title": "Видалити Додаток?", "attachments": "Додатки", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "auto-watch": "Автоматично дивитися дошки, коли вони створені", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Назад", "board-change-color": "Змінити колір", "board-nb-stars": "%s stars", "board-not-found": "Дошка не знайдена", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", - "boardChangeColorPopup-title": "Change Board Background", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", + "boardChangeColorPopup-title": "Змінити Фон Дошки", "boardChangeTitlePopup-title": "Перейменувати дошку", "boardChangeVisibilityPopup-title": "Change Visibility", "boardChangeWatchPopup-title": "Change Watch", "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardChangeViewPopup-title": "Вид Дошки", "boards": "Дошки", - "board-view": "Board View", + "board-view": "Вид Дошки", "board-view-cal": "Календар", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Відміна", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Видалити картку?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Картки", "cards-count": "Картки", + "cards-count-one": "Картка", "casSignIn": "Sign In with CAS", "cardType-card": "Картка", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Закрити", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "чорний", "color-blue": "синій", "color-crimson": "crimson", @@ -230,7 +276,7 @@ "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", "copy-card-link-to-clipboard": "Скопіювати посилання на картку в буфер обміну", "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", + "searchElementPopup-title": "Шукати", "copyCardPopup-title": "Copy Card", "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", @@ -244,30 +290,32 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", - "custom-field-date": "Date", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", + "custom-field-date": "Дата", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", "custom-field-dropdown-options": "List Options", "custom-field-dropdown-options-placeholder": "Press enter to add more options", "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", + "custom-field-number": "Номер", + "custom-field-text": "Текст", "custom-fields": "Custom Fields", - "date": "Date", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", + "date": "Дата", + "decline": "Відхилити", + "default-avatar": "Аватар за замовчуванням", + "delete": "Видалити", "deleteCustomFieldPopup-title": "Delete Custom Field?", "deleteLabelPopup-title": "Delete Label?", - "description": "Description", + "description": "Опис", "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", + "discard": "Відхилити", "done": "Done", - "download": "Download", - "edit": "Edit", + "download": "Завантажити", + "edit": "Редагувати", "edit-avatar": "Змінити аватар", - "edit-profile": "Edit Profile", + "edit-profile": "Редагувати Профіль", "edit-wip-limit": "Edit WIP Limit", "soft-wip-limit": "Soft WIP Limit", "editCardStartDatePopup-title": "Change start date", @@ -276,7 +324,7 @@ "editCardSpentTimePopup-title": "Change spent time", "editLabelPopup-title": "Change Label", "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", + "editProfilePopup-title": "Редагувати Профіль", "email": "Email", "email-enrollAccount-subject": "An account created for you on __siteName__", "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,51 +407,59 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Не видаляйте імпортовані дані з раніше збереженої дошки або Trello, поки не переконаєтеся, що імпорт завершився успішно - вдається закрити і знову відкрити дошку, і не з'являється помилка «Дошка не знайдена», що означає втрату даних.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", - "info": "Version", + "info": "Версія", "initials": "Initials", "invalid-date": "Invalid date", "invalid-time": "Invalid time", "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", + "joined": "приєднано", + "just-invited": "Ви тільки що приєдналися до цієї дошки", "keyboard-shortcuts": "Keyboard shortcuts", "label-create": "Create Label", "label-default": "%s label (default)", "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", + "language": "Мова", + "last-admin-desc": "Ви не можете змінити ролі, бо повинен бути хоча б один адміністратор", "leave-board": "Leave Board", "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", + "leaveBoardPopup-title": "Залишити Дошку?", + "link-card": "Посилання на цю картку", + "list-archive-cards": "Перенести всі картки в цьому переліку до Архиву ", "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", "list-move-cards": "Move all cards in this list", "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", + "set-color-list": "Встановити Колір", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -389,13 +471,15 @@ "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", "members": "Користувачі", - "menu": "Menu", - "move-selection": "Move selection", + "menu": "Меню", + "move-selection": "Перенести обране", "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", + "moveCardToBottom-title": "Перенести до Низу", + "moveCardToTop-title": "Перенести на Початок", + "moveSelectionPopup-title": "Перенести обране", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -421,68 +505,78 @@ "previewAttachedImagePopup-title": "Preview", "previewClipboardImagePopup-title": "Preview", "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", + "private-desc": "Це приватна дошка. Тільки люди, додані до цієї дошки можуть переглядати та редагувати її.", "profile": "Profile", "public": "Public", "public-desc": "Цю дошку можуть переглядати усі, у кого є посилання. Також ця дошка може бути проіндексована пошуковими системами. Вносити зміни можуть тільки учасники.", "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove Cover", + "remove-cover": "Видалити Обкладинку", "remove-from-board": "Remove from Board", "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", + "listDeletePopup-title": "Видалити Список?", + "remove-member": "Видалити Учасника", + "remove-member-from-card": "Видалити з Картки", "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", + "removeMemberPopup-title": "Видалити Учасника?", + "rename": "Перейменувати", "rename-board": "Перейменувати дошку", - "restore": "Restore", - "save": "Save", - "search": "Search", - "rules": "Rules", + "restore": "Відновити", + "save": "Зберегти", + "search": "Шукати", + "rules": "Правила", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", + "search-example": "Write text you search and press Enter", + "select-color": "Оберіть Коліп", + "select-board": "Оберіть Дошку ", + "set-wip-limit-value": "Встановіть обмеження максимальної кількості завдань в цьому списку", "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "Assign yourself to current card", + "shortcut-assign-self": "Призначити себе до поточної картки", "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", + "shortcut-autocomplete-members": "Автозавершення учасників", "shortcut-clear-filters": "Clear all filters", "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", + "shortcut-filter-my-cards": "Фільтрувати мої картки", "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", + "shortcut-toggle-filterbar": "Перемкнути бічну панель Фільтра", + "shortcut-toggle-searchbar": "Перемкнути бічну панель Пошуку", + "shortcut-toggle-sidebar": "Перемкнути бічну панель Дошки", + "show-cards-minimum-count": "Показувати кількість карток, якщо список містить більше, ніж", + "sidebar-open": "Відкрити бокову панель", + "sidebar-close": "Закрити бокову панель", + "signupPopup-title": "Створити Обліковий запис", "star-board-title": "Click to star this board. It will show up at top of your boards list.", "starred-boards": "Starred Boards", "starred-boards-description": "Starred boards show up at the top of your boards list.", "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", + "team": "Команда", + "this-board": "ця дошка", + "this-card": "ця картка", + "spent-time-hours": "Витрачено часу(годин)", "overtime-hours": "Overtime (hours)", "overtime": "Overtime", "has-overtime-cards": "Has overtime cards", "has-spenttime-cards": "Has spent time cards", - "time": "Time", + "time": "Час", "title": "Title", "tracking": "Tracking", "tracking-info": "Ви будете повідомлені про будь-які зміни в тих картках, в яких ви є творцем або учасником.", - "type": "Type", + "type": "Тип", "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", + "unsaved-description": "Ви маєте незбрежений опис", "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", + "upload": "Завантажити", + "upload-avatar": "Завантажити аватар", + "uploaded-avatar": "Завантажений аватар", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", - "view-it": "View it", + "import-usernames": "Import Usernames", + "view-it": "Переглянути це", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", "watching": "Watching", @@ -493,15 +587,15 @@ "welcome-list2": "Advanced", "card-templates-swimlane": "Card Templates", "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", + "board-templates-swimlane": "Шаблони Дошок", + "what-to-do": "Що ви хочете зробити?", + "wipLimitErrorPopup-title": "Некоректне Обмеження WIP", "wipLimitErrorPopup-dialog-pt1": "Кількість завдань у цьому списку перевищує встановлений вами ліміт", "wipLimitErrorPopup-dialog-pt2": "Будь ласка, перенесіть деякі завдання з цього списку або збільште ліміт на кількість завдань", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", + "admin-panel": "Панель Адміністратора", + "settings": "Налаштування", + "people": "Люди", + "registration": "Реєстрація", "disable-self-registration": "Disable Self-Registration", "invite": "Invite", "invite-people": "Invite People", @@ -520,10 +614,10 @@ "invitation-code": "Invitation Code", "email-invite-register-subject": "__inviter__ sent you an invitation", "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", + "email-smtp-test-subject": "Лист перевірки SMTP", + "email-smtp-test-text": "Ви успішно надіслали e-mail", + "error-invitation-code-not-exist": "Код запрошення не існує", + "error-notAuthorized": "Ви не авторизовані для перегляду цієї сторінки.", "webhook-title": "Webhook Name", "webhook-token": "Token (Optional for Authentication)", "outgoing-webhooks": "Outgoing Webhooks", @@ -553,29 +647,32 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", + "accounts-allowEmailChange": "Дозволити Зміну Email", + "accounts-allowUserNameChange": "Дозволити Зміну Імені Користувача", + "createdAt": "Створено", + "modifiedAt": "Modified at", + "verified": "Перевірено", + "active": "Активно", + "card-received": "Отримано", "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", + "card-end": "Кінець", + "card-end-on": "Закінчується на", + "editCardReceivedDatePopup-title": "Змінити дату отримання", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", + "setCardActionsColorPopup-title": "Оберіть колір", + "setSwimlaneColorPopup-title": "Оберіть колір", + "setListColorPopup-title": "Оберіть колір", "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", + "requested-by": "Запитано", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Це видалення назавжди. Ви втратите всі листи, картки та дії, пов'язані з цією дошкою.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", @@ -614,13 +711,16 @@ "r-delete-rule": "Видалити правило", "r-new-rule-name": "Заголовок нового правила\n", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Видалити з", "r-the-board": "Дошка", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "переміщено до", "r-moved-from": "переміщено з", @@ -646,7 +746,7 @@ "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-archive": "Move to Archive", + "r-archive": "Перенести до Архіву", "r-unarchive": "Restore from Archive", "r-card": "Картка", "r-add": "Додати", @@ -659,12 +759,13 @@ "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", + "r-check": "Обрати", + "r-uncheck": "Відхилити Обрання", + "r-item": "одиниця", "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "Об'єкт", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -675,16 +776,16 @@ "r-d-send-email-to": "to", "r-d-send-email-subject": "Об'єкт", "r-d-send-email-message": "повідомлення", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", + "r-d-archive": "Перенести Картку до Архиву", + "r-d-unarchive": "Відновити Картку з Архіву", + "r-d-add-label": "Додатку мітку", + "r-d-remove-label": "Видалити мітку", + "r-create-card": "Створити нову картку", + "r-in-list": "в переліку", "r-in-swimlane": "in swimlane", "r-d-add-member": "Додати користувача", "r-d-remove-member": "Видалити користувача", - "r-d-remove-all-member": "Remove all member", + "r-d-remove-all-member": "Видалити всіх учасників", "r-d-check-all": "Check all items of a list", "r-d-uncheck-all": "Uncheck all items of a list", "r-d-check-one": "Check item", @@ -702,19 +803,19 @@ "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", "r-when-a-card-is-moved": "When a card is moved to another list", "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", + "r-update": "Оновити", + "r-datefield": "поле для дати", + "r-df-start-at": "початок", + "r-df-due-at": "до", + "r-df-end-at": "кінець", + "r-df-received-at": "отримано", "r-to-current-datetime": "to current date/time", "r-remove-value-from": "Remove value from", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", + "authentication-method": "метод Автентифікації", + "authentication-type": "тип Автентифікації", "custom-product-name": "Custom Product Name", "layout": "Layout", "hide-logo": "Hide Logo", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "Ви були згадані у [__board__] __list__/__card__", "delete-user-confirm-popup": "Ви дійсно бажаєте видалити даний обліковий запис? Цю дію не можна відмінити.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Дозволити користувачам видаляти їх власні облікові записи", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Статус", + "swimlane": "Swimlane", + "owner": "Власник", + "last-modified-at": "Востаннє змінено", + "last-activity": "Остання активність", + "voting": "Голосування", + "archived": "Архівовано", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Сховати обрані елементи", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Картка", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "Користувач", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "до", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "до", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "початок", + "predicate-end": "кінець", + "predicate-assignee": "assignee", + "predicate-member": "Користувач", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Номер", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/vi.i18n.json b/i18n/vi.i18n.json index eacb81fc4..ef0f31829 100644 --- a/i18n/vi.i18n.json +++ b/i18n/vi.i18n.json @@ -1,55 +1,55 @@ { "accept": "Chấp nhận", "act-activity-notify": "Thông báo hoạt động", - "act-addAttachment": "thêm tập tin đính kèm __attachment__ vào thẻ __list__ tại danh sách __list__ tại đường bơi __swimlane__ tại bảng __board__", - "act-deleteAttachment": "xóa tập tin đính kèm __attachment__ tại thẻ __card__ tại danh sách __list__ tại đường bơi __swimlane__ tại bảng __board__ ", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-addAttachment": "thêm tập tin đính kèm __attachment__ vào thẻ __list__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-deleteAttachment": "xóa tập tin đính kèm __attachment__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__ ", + "act-addSubtask": "đã thêm nhiệm vụ con __subtask__ vào thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ at board __board__", + "act-addLabel": "Đã thêm nhãn __label__ vào thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-addedLabel": "Đã thêm nhãn __label__ vào thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-removeLabel": "Đã xóa nhãn __label__ từ thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-removedLabel": "Đã xóa nhãn __label__ từ thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-addChecklist": "đã thêm checklist __checklist__ vào thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-addChecklistItem": "đã thêm mục checklist __checklistItem__ vào checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-removeChecklist": "đã xóa checklist __checklist__ từ thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-removeChecklistItem": "đã xóa mục checklist __checklistItem__ từ checklist __checkList__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-checkedItem": "đã đánh dấu hoàn thành mục __checklistItem__ của checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-uncheckedItem": "đã bỏ đánh dấu hoàn thành mục __checklistItem__ của checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-completeChecklist": "đã hoàn thành checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-uncompleteChecklist": "đã bỏ đánh dấu hoàn thành checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-addComment": "đã bình luận trên thẻ __card__: __comment__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-editComment": "đã sửa bình luận trên thẻ __card__: __comment__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-deleteComment": "đã xóa bình luận trên thẻ __card__: __comment__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-createBoard": "đã tạo bảng __board__", + "act-createSwimlane": "đã tạo làn ngang __swimlane__ tới bảng __board__", + "act-createCard": "đã tạo thẻ __card__ vào danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-createCustomField": "đã tạo trường __customField__ tại bảng __board__", + "act-deleteCustomField": "đã xóa trường tùy chỉnh __customField__ tại bảng __board__", + "act-setCustomField": "trường tùy chỉnh đã chỉnh sửa __customField__: __customFieldValue__ Tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-createList": "đã thêm danh sách __list__ vàng bảng __board__", + "act-addBoardMember": "đã thêm thành viên __member__ vào bảng __board__", + "act-archivedBoard": "Bảng __board__ đã được chuyển đến Lưu trữ", + "act-archivedCard": "Thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__ được chuyển vào Lưu trữ", + "act-archivedList": "Danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__ đã chuyển đến Lưu trữ", + "act-archivedSwimlane": "Làn ngang __swimlane__ tại bảng __board__ được chuyển đến Lưu trữ", + "act-importBoard": "đã nhập bảng __board__", + "act-importCard": "đã nhập thẻ __card__ vào danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-importList": "đã nhập danh sách __list__ vào làn ngang __swimlane__ tại bảng __board__", + "act-joinMember": "đã thêm thành viên __member__ vào thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ at board __board__", + "act-moveCard": "đã di chuyển thẻ __card__ tại bảng __board__ từ danh sách __oldList__ tại làn ngang __oldSwimlane__ tới danh sách __list__ tại làn ngang __swimlane__", + "act-moveCardToOtherBoard": "đã di chuyển thẻ __card__ từ danh sách __oldList__ tại làn ngang __oldSwimlane__ tại bảng __oldBoard__ tới danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-removeBoardMember": "đã xóa thành viên __member__ khỏi bảng __board__", + "act-restoredCard": "đã khôi phục thẻ __card__ vào danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "act-unjoinMember": "đã xóa thành viên __member__ từ thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "Hành Động", "activities": "Hoạt Động", "activity": "Hoạt Động", "activity-added": "đã thêm %s vào %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "đã chuyển %s đến Lưu trữ", "activity-attached": "đã đính kèm %s vào %s", "activity-created": "đã tạo %s", - "activity-customfield-created": "created custom field %s", + "activity-customfield-created": "đã tạo trường tuỳ chỉnh %s", "activity-excluded": "đã loại bỏ %s khỏi %s", "activity-imported": "đã nạp %s vào %s từ %s", "activity-imported-board": "đã nạp %s từ %s", @@ -59,27 +59,34 @@ "activity-removed": "đã xóa %s từ %s", "activity-sent": "gửi %s đến %s", "activity-unjoined": "đã rời khỏi %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-subtask-added": "đã thêm subtask vào %s", + "activity-checked-item": "đã đánh dấu hoàn thành %s trong checklist %s của %s", + "activity-unchecked-item": "đã bỏ đánh dấu hoàn thành %s trong checklist %s của %s", "activity-checklist-added": "đã thêm checklist vào %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "activity-checklist-removed": "đã xóa một checklist từ %s", + "activity-checklist-completed": "đã đánh dấu hoàn thành toàn bộ checklist %s của %s", + "activity-checklist-uncompleted": "đánh dấu chưa hoàn thành toàn bộ checklist %s của %s", + "activity-checklist-item-added": "đã thêm mục checklist vào '%s' trong %s", + "activity-checklist-item-removed": "đã xóa một mục checklist từ '%s' trong %s", "add": "Thêm", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", + "activity-checked-item-card": "đã hoàn thành %s trong checklist %s", + "activity-unchecked-item-card": "đã bỏ đánh dấu hoàn thành %s trong checklist %s", + "activity-checklist-completed-card": "đã hoàn thành checklist __checklist__ tại thẻ __card__ tại danh sách __list__ tại làn ngang __swimlane__ tại bảng __board__", + "activity-checklist-uncompleted-card": "chưa hoàn thành checklist", + "activity-editComment": "lời bình đã sửa", + "activity-deleteComment": "đã xoá lời bình %s", + "activity-receivedDate": "đã sửa ngày nhận đến %s của %s", + "activity-startDate": "đã sửa ngày bắt đầu thành %s của %s", + "activity-dueDate": "đã sửa ngày hết hạn thành %s của %s", + "activity-endDate": "đã sửa ngày kết thúc thành %s của %s", "add-attachment": "Thêm Bản Đính Kèm", "add-board": "Thêm Bảng", + "add-template": "Add Template", "add-card": "Thêm Thẻ", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", + "add-swimlane": "Thêm làn ngang", + "add-subtask": "Thêm Nhiệm vụ phụ", "add-checklist": "Thêm Danh Sách Kiểm Tra", "add-checklist-item": "Thêm Một Mục Vào Danh Sách Kiểm Tra", "add-cover": "Thêm Bìa", @@ -90,29 +97,31 @@ "addMemberPopup-title": "Thành Viên", "admin": "Quản Trị Viên", "admin-desc": "Có thể xem và chỉnh sửa những thẻ, xóa thành viên và thay đổi cài đặt cho bảng.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", + "admin-announcement": "Thông báo", + "admin-announcement-active": "Thông báo trên toàn hệ thống đang hoạt động", + "admin-announcement-title": "Thông báo từ Quản trị viên", "all-boards": "Tất cả các bảng", "and-n-other-card": "Và __count__ thẻ khác", "and-n-other-card_plural": "Và __count__ thẻ khác", - "apply": "Ứng Dụng", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", + "apply": "Áp Dụng", + "app-is-offline": "Đang tải, vui lòng đợi. Làm mới trang sẽ làm mất dữ liệu. Nếu quá trình tải không hoạt động, vui lòng kiểm tra lại máy chủ.", + "archive": "Di chuyển đến Lưu trữ", + "archive-all": "Di chuyển tất cả vào Lưu trữ", + "archive-board": "Di chuyển Bảng sang Lưu trữ", + "archive-card": "Di chuyển Thẻ vào Lưu trữ", + "archive-list": "Di chuyển Danh sách vào Lưu trữ", + "archive-swimlane": "Di chuyển Làn ngang vào Lưu trữ", + "archive-selection": "Di chuyển lựa chọn vào Lưu trữ", + "archiveBoardPopup-title": "Chuyển Bảng sang Lưu trữ?", "archived-items": "Lưu Trữ", - "archived-boards": "Boards in Archive", + "archived-boards": "Các bảng trong lưu trữ", "restore-board": "Khôi Phục Bảng", - "no-archived-boards": "No Boards in Archive.", + "no-archived-boards": "Không có bảng nào trong Lưu trữ.", "archives": "Lưu Trữ", - "template": "Template", - "templates": "Templates", + "template": "Mẫu", + "templates": "Các mẫu", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Chỉ định thành viên", "attached": "đã đính kèm", "attachment": "Phần đính kèm", @@ -120,77 +129,114 @@ "attachmentDeletePopup-title": "Xóa tệp đính kèm không?", "attachments": "Tệp Đính Kèm", "auto-watch": "Tự động xem bảng lúc được tạo ra", - "avatar-too-big": "Hình đại diện quá to (70KB tối đa)", + "avatar-too-big": "Hình đại diện quá lớn (tối đa 520KB)", "back": "Trở Lại", "board-change-color": "Đổi màu", "board-nb-stars": "%s sao", "board-not-found": "Không tìm được bảng", - "board-private-info": "Bảng này sẽ chuyển sang <strong> chế độ private</strong>.", - "board-public-info": "Bảng này sẽ chuyển sang <strong> chế độ public</strong>.", + "board-private-info": "Bảng này sẽ chuyển sang chế độ <strong>riêng tư</strong>.", + "board-public-info": "Bảng này sẽ chuyển sang chế độ <strong>công khai</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Thay hình nền của bảng", "boardChangeTitlePopup-title": "Đổi tên bảng", "boardChangeVisibilityPopup-title": "Đổi cách hiển thị", "boardChangeWatchPopup-title": "Đổi cách xem", - "boardMenuPopup-title": "Board Settings", - "boardChangeViewPopup-title": "Board View", + "boardMenuPopup-title": "Cài đặt Bảng", + "boardChangeViewPopup-title": "Kiểu xem Bảng", "boards": "Bảng", - "board-view": "Board View", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-lists": "Lists", - "bucket-example": "Like “Bucket List” for example", + "board-view": "Kiểu xem Bảng", + "board-view-cal": "Lịch", + "board-view-swimlanes": "Làn ngang", + "board-view-collapse": "Thu gọn", + "board-view-gantt": "Biểu đồ Gantt", + "board-view-lists": "Danh sách", + "bucket-example": "Ví dụ như \"Danh sách nhóm\"", "cancel": "Hủy", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "Thẻ này đã được chuyển đến Lưu trữ.", + "board-archived": "Bảng này đã được chuyển đến Lưu trữ.", "card-comments-title": "Thẻ này có %s bình luận.", "card-delete-notice": "Hành động xóa là không thể khôi phục. Bạn sẽ mất hết các hoạt động liên quan đến thẻ này.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardDeletePopup-title": "Delete Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", + "card-delete-pop": "Tất cả các hành động sẽ bị xóa khỏi nguồn cấp dữ liệu hoạt động và bạn sẽ không thể mở lại thẻ. Không có hoàn tác.", + "card-delete-suggest-archive": "Bạn có thể di chuyển một thẻ vào Lưu trữ để xóa thẻ đó khỏi bảng và duy trì hoạt động này.", + "card-due": "Đến hạn", + "card-due-on": "Đến hạn vào", + "card-spent": "Thời lượng", + "card-edit-attachments": "Sửa tệp đính kèm", + "card-edit-custom-fields": "Sửa trường tuỳ chỉnh", + "card-edit-labels": "Sửa nhãn", + "card-edit-members": "Sửa thành viên", + "card-labels-title": "Thay đổi nhãn cho Thẻ", + "card-members-title": "Thêm hoặc xóa các thành viên của bảng khỏi thẻ.", + "card-start": "Bắt đầu", + "card-start-on": "Bắt đầu vào", + "cardAttachmentsPopup-title": "Đính kèm từ", + "cardCustomField-datePopup-title": "Thay đổi ngày", + "cardCustomFieldsPopup-title": "Sửa trường tuỳ chỉnh", + "cardStartVotingPopup-title": "Bắt đầu một cuộc bỏ phiếu", + "positiveVoteMembersPopup-title": "Những người ủng hộ", + "negativeVoteMembersPopup-title": "Những người phản đối", + "card-edit-voting": "Sửa bình chọn", + "editVoteEndDatePopup-title": "Thay đổi ngày kết thúc phiếu bầu", + "allowNonBoardMembers": "Cho phép tất cả người dùng đã đăng nhập", + "vote-question": "Câu hỏi bình chọn", + "vote-public": "Hiển thị ai đã bình chọn cái gì", + "vote-for-it": "tán thành", + "vote-against": "phản đối", + "deleteVotePopup-title": "Xoá bình chọn?", + "vote-delete-pop": "Việc xóa là vĩnh viễn. Bạn sẽ mất tất cả các hành động liên quan đến phiếu bầu này.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", + "cardDeletePopup-title": "Xoá Thẻ", + "cardDetailsActionsPopup-title": "Hành động thẻ", + "cardLabelsPopup-title": "Nhãn", "cardMembersPopup-title": "Thành Viên", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "cardMorePopup-title": "Thêm nữa", + "cardTemplatePopup-title": "Tạo mẫu", + "cards": "Thẻ", + "cards-count": "Thẻ", + "cards-count-one": "Thẻ", + "casSignIn": "Đăng nhập bằng CAS", + "cardType-card": "Thẻ", + "cardType-linkedCard": "Thẻ đã liên kết", + "cardType-linkedBoard": "Bảng đã liên kết", + "change": "Thay đổi", + "change-avatar": "Thay đổi hình đại diện", + "change-password": "Đổi mật khẩu", + "change-permissions": "Thay đổi quyền", + "change-settings": "Thay đổi Cài đặt", + "changeAvatarPopup-title": "Thay đổi hình đại diện", + "changeLanguagePopup-title": "Thay đổi ngôn ngữ", + "changePasswordPopup-title": "Đổi mật khẩu", + "changePermissionsPopup-title": "Thay đổi quyền", + "changeSettingsPopup-title": "Thay đổi Cài đặt", + "subtasks": "Nhiệm vụ phụ", + "checklists": "Danh sách việc cần hoàn thành", + "click-to-star": "Bấm để gắn dấu sao bảng này.", + "click-to-unstar": "Bấm để bỏ gắn bảng này.", + "clipboard": "Clipboard hoặc kéo và thả", + "close": "Đóng", + "close-board": "Đóng bảng", + "close-board-pop": "Bạn sẽ có thể khôi phục bảng bằng cách nhấp vào nút \"Lưu trữ\" từ tiêu đề trang chủ.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -216,554 +262,801 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", - "unset-color": "Unset", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments and activities.", + "unset-color": "Không đặt", + "comment": "Bình luận", + "comment-placeholder": "Viết Bình Luận", + "comment-only": "Chỉ bình luận", + "comment-only-desc": "Chỉ có thể nhận xét về thẻ.", + "no-comments": "Không có bình luận", + "no-comments-desc": "Không thể xem bình luận và hoạt động.", "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", - "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", + "worker-desc": "Chỉ có thể di chuyển thẻ, tự gán thẻ và nhận xét.", + "computer": "Máy tính", + "confirm-subtask-delete-dialog": "Bạn có chắc chắn muốn xóa nhiệm vụ phụ không?", + "confirm-checklist-delete-dialog": "Bạn có chắc chắn muốn xóa checklist không?", + "copy-card-link-to-clipboard": "Sao chép liên kết thẻ vào khay nhớ tạm", + "linkCardPopup-title": "Thẻ liên kết", + "searchElementPopup-title": "Tìm kiếm", + "copyCardPopup-title": "Sao chép Thẻ", + "copyChecklistToManyCardsPopup-title": "Sao chép Mẫu Checklist cho Nhiều thẻ", + "copyChecklistToManyCardsPopup-instructions": "Tiêu đề và mô tả thẻ đích ở định dạng JSON này", + "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Tiêu đề thẻ đầu tiên\", \"description\":\"Mô tả thẻ đầu tiên\"}, {\"title\":\"Tiêu đề thẻ thứ hai\",\"description\":\"Mô tả thẻ thứ hai\"},{\"title\":\"Tiêu đề thẻ cuối cùng\",\"description\":\"Mô tả thẻ cuối cùng\"} ]", + "create": "Tạo", + "createBoardPopup-title": "Tạo Bảng", + "chooseBoardSourcePopup-title": "Nhập bảng", + "createLabelPopup-title": "Tạo nhãn", + "createCustomField": "Tạo Trường", + "createCustomFieldPopup-title": "Tạo Trường", + "current": "hiện tại", + "custom-field-delete-pop": "Không thể hoàn tác. Thao tác này sẽ xóa trường tùy chỉnh này khỏi tất cả các thẻ và hủy lịch sử của nó.", + "custom-field-checkbox": "Hộp kiểm", + "custom-field-currency": "Tiền tệ", + "custom-field-currency-option": "Mã tiền tệ", + "custom-field-date": "Ngày", + "custom-field-dropdown": "Danh sách thả xuống", "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", + "custom-field-dropdown-options": "Tùy chọn danh sách", + "custom-field-dropdown-options-placeholder": "Nhấn enter để thêm các tùy chọn khác", + "custom-field-dropdown-unknown": "(không xác định)", + "custom-field-number": "Số", + "custom-field-text": "Văn bản", + "custom-fields": "Trường tùy chỉnh", + "date": "Ngày", + "decline": "Từ chối", + "default-avatar": "Hình đại diện mặc định", + "delete": "Xoá", + "deleteCustomFieldPopup-title": "Xoá Trường tuỳ chỉnh?", + "deleteLabelPopup-title": "Xoá Nhãn?", + "description": "Mô tả", + "disambiguateMultiLabelPopup-title": "Đánh dấu nhãn hành động", + "disambiguateMultiMemberPopup-title": "Làm rõ hành động của thành viên", + "discard": "Bỏ đi", + "done": "Xong", + "download": "Tải về", + "edit": "Sửa", + "edit-avatar": "Thay đổi hình đại diện", + "edit-profile": "Sửa Hồ sơ", + "edit-wip-limit": "Chỉnh sửa giới hạn WIP", + "soft-wip-limit": "Giới hạn Soft WIP", + "editCardStartDatePopup-title": "Thay đổi ngày bắt đầu", + "editCardDueDatePopup-title": "Thay đổi ngày đến hạn", + "editCustomFieldPopup-title": "Sửa Trường", + "editCardSpentTimePopup-title": "Thay đổi thời gian đã sử dụng", + "editLabelPopup-title": "Thay đổi nhãn", + "editNotificationPopup-title": "Sửa Thông báo", + "editProfilePopup-title": "Sửa Hồ sơ", "email": "Email", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "sort": "Sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", + "email-enrollAccount-subject": "Một tài khoản được tạo cho bạn trên __siteName__", + "email-enrollAccount-text": "Chào __user__,\n\nĐể bắt đầu sử dụng dịch vụ, chỉ cần nhấp vào liên kết bên dưới.\n\n__url__\n\nCảm ơn.", + "email-fail": "Gửi email không thành công", + "email-fail-text": "Lỗi khi gửi email", + "email-invalid": "Email không hợp lệ", + "email-invite": "Mời qua Email", + "email-invite-subject": "__inviter__ đã gửi cho bạn lời mời", + "email-invite-text": "Gửi __user__,\n\n__inviter__ mời bạn tham gia bảng \"__board__\" để cộng tác.\n\nVui lòng theo liên kết bên dưới:\n\n__url__\n\nCảm ơn.", + "email-resetPassword-subject": "Đặt lại mật khẩu của bạn trên __siteName__", + "email-resetPassword-text": "Chào __user__,\n\nĐể đặt lại mật khẩu của bạn, chỉ cần nhấp vào liên kết bên dưới.\n\n__url__\n\nCảm ơn.", + "email-sent": "Đã gửi email", + "email-verifyEmail-subject": "Xác minh địa chỉ email của bạn trên __siteName__", + "email-verifyEmail-text": "Chào __user__,\n\nĐể xác minh email tài khoản của bạn, chỉ cần nhấp vào liên kết bên dưới.\n\n__url__\n\nCảm ơn.", + "enable-wip-limit": "Bật giới hạn WIP", + "error-board-doesNotExist": "Bảng này không tồn tại", + "error-board-notAdmin": "Bạn cần phải là quản trị viên của bảng này để làm điều đó", + "error-board-notAMember": "Bạn cần phải là thành viên của bảng này để làm điều đó", + "error-json-malformed": "Văn bản của bạn không phải là JSON hợp lệ", + "error-json-schema": "Dữ liệu JSON của bạn không bao gồm thông tin thích hợp ở định dạng chính xác", + "error-csv-schema": "CSV (Giá trị được phân tách bằng dấu phẩy)/TSV (Giá trị được phân cách bằng tab) của bạn không bao gồm thông tin thích hợp ở định dạng chính xác", + "error-list-doesNotExist": "Danh sách này không tồn tại", + "error-user-doesNotExist": "Người dùng này không tồn tại", + "error-user-notAllowSelf": "Bạn không thể mời chính mình", + "error-user-notCreated": "Người dùng này không được tạo", + "error-username-taken": "Tên người dùng này đã được sử dụng", + "error-orgname-taken": "Tên tổ chức này đã được sử dụng", + "error-teamname-taken": "Tên team này đã được sử dụng", + "error-email-taken": "Email đã được sử dụng", + "export-board": "Xuất bảng", + "export-board-json": "Xuất bảng sang JSON", + "export-board-csv": "Xuất bảng sang CSV", + "export-board-tsv": "Xuất bảng sang TSV", + "export-board-excel": "Xuất bảng sang Excel", + "user-can-not-export-excel": "Người dùng không thể xuất Excel", + "export-board-html": "Xuất bảng sang HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Xuất bảng", + "exportCardPopup-title": "Export card", + "sort": "Sắp xếp", + "sort-desc": "Nhấp để sắp xếp danh sách", + "list-sort-by": "Sắp xếp Danh sách bởi:", + "list-label-modifiedAt": "Lần truy cập cuối cùng", + "list-label-title": "Tên của danh sách", "list-label-sort": "Your Manual Order", "list-label-short-modifiedAt": "(L)", "list-label-short-title": "(N)", "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-no-label": "No label", - "filter-no-member": "No member", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "hide-system-messages": "Hide system messages", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", + "filter": "Bộ lọc", + "filter-cards": "Lọc thẻ hoặc danh sách", + "filter-dates-label": "Lọc theo ngày", + "filter-no-due-date": "Không có ngày đến hạn", + "filter-overdue": "Quá hạn", + "filter-due-today": "Đến hạn hôm nay", + "filter-due-this-week": "Đến hạn trong tuần này", + "filter-due-tomorrow": "Đến hạn vào ngày mai", + "list-filter-label": "Lọc danh sách theo tiêu đề", + "filter-clear": "Xóa bộ lọc", + "filter-labels-label": "Lọc theo nhãn", + "filter-no-label": "Không có nhãn", + "filter-member-label": "Lọc theo thành viên", + "filter-no-member": "Không có thành viên", + "filter-assignee-label": "Lọc theo người được giao", + "filter-no-assignee": "Không có người được giao", + "filter-custom-fields-label": "Lọc theo Trường tuỳ chỉnh", + "filter-no-custom-fields": "Không có trường tùy chỉnh", + "filter-show-archive": "Hiển thị danh sách đã lưu trữ", + "filter-hide-empty": "Ẩn danh sách trống", + "filter-on": "Bộ lọc đang bật", + "filter-on-desc": "Bạn đang lọc thẻ trên bảng này. Nhấn vào đây để chỉnh sửa bộ lọc.", + "filter-to-selection": "Lọc lựa chọn", + "other-filters-label": "Bộ lọc khác", + "advanced-filter-label": "Bộ lọc nâng cao", + "advanced-filter-description": "Bộ lọc Nâng cao cho phép viết một chuỗi chứa các toán tử sau: == != <= >= && || ( ) Một khoảng trắng được sử dụng làm dấu phân cách giữa các Toán tử. Bạn có thể lọc tất cả các Trường tùy chỉnh bằng cách nhập tên và giá trị của chúng. Ví dụ: Field1 == Value1. Chú ý: Nếu các trường hoặc giá trị chứa khoảng trắng, bạn cần phải đóng gói chúng thành các dấu ngoặc kép. Ví dụ: 'Field 1' == 'Value 1'. Để bỏ qua các ký tự điều khiển đơn (' \\/) bạn có thể sử dụng \\. Ví dụ: Field1 == I\\'m. Ngoài ra bạn có thể kết hợp nhiều điều kiện. Ví dụ: F1 == V1 || F1 == V2. Thông thường tất cả các toán tử được giải thích từ trái sang phải. Bạn có thể thay đổi thứ tự bằng cách đặt dấu ngoặc. Ví dụ: F1 == V1 && ( F2 == V2 || F2 == V3 ). Ngoài ra, bạn có thể tìm kiếm các trường văn bản bằng cách sử dụng regex: F1 == /Tes.*/i", + "fullname": "Họ và tên", + "header-logo-title": "Quay lại trang bảng của bạn.", + "hide-system-messages": "Ẩn tin nhắn hệ thống", + "headerBarCreateBoardPopup-title": "Tạo Bảng", + "home": "Trang Chủ", + "import": "Nhập khẩu", + "impersonate-user": "Mạo danh người dùng", + "link": "Liên kết", + "import-board": "nhập bảng", + "import-board-c": "Nhập bảng", + "import-board-title-trello": "Nhập bảng từ Trello", + "import-board-title-wekan": "Nhập bảng từ lần xuất trước", + "import-board-title-csv": "Nhập bảng từ CSV/TSV", + "from-trello": "Từ Trello", + "from-wekan": "Từ lần xuất trước", + "from-csv": "Từ CSV/TSV", + "import-board-instruction-trello": "Trong bảng Trello của bạn, đi tới 'Trình đơn', sau đó đến 'Khác', 'In và Xuất', 'Xuất JSON' và sao chép văn bản kết quả.", + "import-board-instruction-csv": "Dán Giá trị được phân tách bằng dấu phẩy(CSV)/ Giá trị được phân cách bằng tab (TSV) của bạn.", + "import-board-instruction-wekan": "Trong bảng của bạn, hãy chuyển đến \"Menu\", sau đó \"Xuất bảng\" và sao chép văn bản trong tệp đã tải xuống.", + "import-board-instruction-about-errors": "Nếu bạn gặp lỗi khi nhập bảng, đôi khi quá trình nhập vẫn hoạt động và bảng ở trang Tất cả bảng.", + "import-json-placeholder": "Dán dữ liệu JSON hợp lệ của bạn vào đây", + "import-csv-placeholder": "Dán dữ liệu CSV/TSV hợp lệ của bạn vào đây", + "import-map-members": "Bản đồ thành viên", + "import-members-map": "Bảng nhập khẩu của bạn có một số thành viên. Vui lòng ánh xạ các thành viên bạn muốn nhập với người dùng của mình", + "import-members-map-note": "Lưu ý: Các thành viên chưa được ánh xạ sẽ được chỉ định cho người dùng hiện tại.", + "import-show-user-mapping": "Đánh giá bản đồ thành viên", + "import-user-select": "Chọn người dùng hiện tại của bạn mà bạn muốn sử dụng làm thành viên này", + "importMapMembersAddPopup-title": "Chọn thành viên", + "info": "Phiên bản", + "initials": "Tên viết tắt", + "invalid-date": "Ngày không hợp lệ", + "invalid-time": "Thời gian không hợp lệ", + "invalid-user": "Người dùng không hợp lệ", + "joined": "đã tham gia", + "just-invited": "Bạn vừa được mời vào bảng này", + "keyboard-shortcuts": "Các phím tắt", + "label-create": "Tạo nhãn", + "label-default": "nhãn %s (mặc định)", + "label-delete-pop": "Không có hoàn tác. Thao tác này sẽ xóa nhãn này khỏi tất cả các thẻ và hủy lịch sử của thẻ.", + "labels": "Nhãn", + "language": "Ngôn ngữ", + "last-admin-desc": "Bạn không thể thay đổi vai trò vì phải có ít nhất một quản trị viên.", + "leave-board": "Rời khỏi Bảng", + "leave-board-pop": "Bạn có chắc chắn muốn rời khỏi __boardTitle__ không? Bạn sẽ bị xóa khỏi tất cả các Thẻ trên bảng này.", + "leaveBoardPopup-title": "Rời khỏi Bảng?", + "link-card": "Liên kết đến thẻ này", + "list-archive-cards": "Di chuyển tất cả các thẻ trong danh sách này vào Lưu trữ", + "list-archive-cards-pop": "Thao tác này sẽ xóa tất cả các thẻ trong danh sách này khỏi bảng. Để xem thẻ trong Lưu trữ và đưa chúng trở lại bảng, hãy nhấp vào “Trình đơn”> “Lưu trữ”.", + "list-move-cards": "Di chuyển tất cả các thẻ trong danh sách này", + "list-select-cards": "Chọn tất cả các thẻ trong danh sách này", + "set-color-list": "Đặt Màu", + "listActionPopup-title": "Liệt kê các hành động", + "settingsUserPopup-title": "Cài đặt người dùng", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", + "swimlaneActionPopup-title": "Hành động trên Làn ngang", + "swimlaneAddPopup-title": "Thêm một Làn ngang bên dưới", + "listImportCardPopup-title": "Nhập thẻ Trello", + "listImportCardsTsvPopup-title": "Nhập Excel CSV/TSV", + "listMorePopup-title": "Thêm nữa", + "link-list": "Liên kết đến danh sách này", + "list-delete-pop": "Tất cả các hành động sẽ bị xóa khỏi nguồn cấp dữ liệu hoạt động và bạn sẽ không thể khôi phục danh sách. Không có hoàn tác.", + "list-delete-suggest-archive": "Bạn có thể di chuyển danh sách vào Lưu trữ để xóa danh sách đó khỏi Bảng và duy trì hoạt động.", + "lists": "Danh sách", + "swimlanes": "Làn ngang", + "log-out": "Đăng Xuất", + "log-in": "Đăng nhập", + "loginPopup-title": "Đăng nhập", + "memberMenuPopup-title": "Cài đặt thành viên", "members": "Thành Viên", "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "multi-selection": "Multi-Selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creater or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", + "move-selection": "Di chuyển lựa chọn", + "moveCardPopup-title": "Di chuyển Thẻ", + "moveCardToBottom-title": "Di chuyển xuống dưới cùng", + "moveCardToTop-title": "Di chuyển lên đầu", + "moveSelectionPopup-title": "Di chuyển lựa chọn", + "multi-selection": "Chọn nhiều", + "multi-selection-label": "Đặt nhãn để lựa chọn", + "multi-selection-member": "Đặt thành viên để lựa chọn", + "multi-selection-on": "Chọn nhiều Đang bật", + "muted": "Đã tắt tiếng", + "muted-info": "Bạn sẽ không bao giờ được thông báo về bất kỳ thay đổi nào trong bảng này", + "my-boards": "Bảng của tôi", + "name": "Tên", + "no-archived-cards": "Không có thẻ nào trong Lưu trữ.", + "no-archived-lists": "Không có danh sách nào trong Lưu trữ.", + "no-archived-swimlanes": "Không có Làn ngang nào trong Lưu trữ.", + "no-results": "Không có kết quả", + "normal": "Bình thường", + "normal-desc": "Có thể xem và chỉnh sửa thẻ. Không thể thay đổi cài đặt.", + "not-accepted-yet": "Lời mời chưa được chấp nhận", + "notify-participate": "Nhận thông tin cập nhật cho bất kỳ thẻ nào bạn tham gia với tư cách là người sáng tạo hoặc thành viên", + "notify-watch": "Nhận thông tin cập nhật cho bất kỳ bảng, danh sách hoặc thẻ nào bạn đang xem", + "optional": "không bắt buộc", + "or": "hoặc", + "page-maybe-private": "Trang này có thể là riêng tư. Bạn có thể xem nó bằng cách <a href='%s'>đăng nhập.</a>", + "page-not-found": "Không tìm thấy trang.", + "password": "Mật khẩu", + "paste-or-dragdrop": "để dán, hoặc kéo & thả tệp hình ảnh vào đó (chỉ hình ảnh)", + "participating": "Tham gia", + "preview": "Xem trước", + "previewAttachedImagePopup-title": "Xem trước", + "previewClipboardImagePopup-title": "Xem trước", + "private": "Riêng tư", + "private-desc": "Bảng này là riêng tư. Chỉ những người được thêm vào bảng mới có thể xem và chỉnh sửa nó.", + "profile": "Hồ sơ", + "public": "Công khai", + "public-desc": "Bảng này là công khai. Nó hiển thị với bất kỳ ai có liên kết và sẽ hiển thị trong các công cụ tìm kiếm như Google. Chỉ những người được thêm vào Bảng mới có thể chỉnh sửa.", + "quick-access-description": "Gắn dấu sao bảng để thêm lối tắt trong thanh này.", "remove-cover": "Remove Cover", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", + "remove-from-board": "Xóa khỏi Bảng", + "remove-label": "Xóa nhãn", + "listDeletePopup-title": "Xoá Danh sách?", + "remove-member": "Loại bỏ Thành viên", + "remove-member-from-card": "Xóa khỏi Thẻ", + "remove-member-pop": "Xóa __name__ (__username__) khỏi __boardTitle__? Thành viên sẽ bị xóa khỏi tất cả các thẻ trên bảng này. Họ sẽ nhận được một thông báo.", + "removeMemberPopup-title": "Xóa Thành Viên?", + "rename": "Đổi tên", "rename-board": "Đổi tên bảng", - "restore": "Restore", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", - "select-color": "Select Color", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", + "restore": "Khôi phục", + "save": "Lưu", + "search": "Tìm kiếm", + "rules": "Quy tắc", + "search-cards": "Tìm kiếm từ Tiêu đề Thẻ/Danh sách, Mô tả và các trường tùy chỉnh trên bảng này", + "search-example": "Nhập từ khoá tìm kiếm và ấn Enter", + "select-color": "Chọn màu", + "select-board": "Chọn Bảng", + "set-wip-limit-value": "Đặt giới hạn cho số lượng nhiệm vụ tối đa trong danh sách này", + "setWipLimitPopup-title": "Đặt giới hạn WIP", + "shortcut-assign-self": "Chỉ định bạn vào thẻ hiện tại", + "shortcut-autocomplete-emoji": "Tự động điền biểu tượng cảm xúc", + "shortcut-autocomplete-members": "Tự động điền Thành viên", + "shortcut-clear-filters": "Xóa tất cả các bộ lọc", + "shortcut-close-dialog": "Đóng hộp thoại", + "shortcut-filter-my-cards": "Lọc thẻ của tôi", + "shortcut-show-shortcuts": "Hiển thị danh sách phím tắt", + "shortcut-toggle-filterbar": "Chuyển đổi Thanh bên Bộ lọc", + "shortcut-toggle-searchbar": "Chuyển đổi Thanh bên Tìm kiếm", + "shortcut-toggle-sidebar": "Chuyển đổi thanh bên bảng", + "show-cards-minimum-count": "Hiển thị số lượng thẻ nếu danh sách chứa nhiều hơn", + "sidebar-open": "Mở Thanh Bên", + "sidebar-close": "Đóng Thanh bên", + "signupPopup-title": "Tạo Tài Khoản", + "star-board-title": "Bấm để gắn dấu sao bảng này. Nó sẽ hiển thị ở đầu danh sách bảng của bạn.", + "starred-boards": "Bảng có gắn dấu sao", + "starred-boards-description": "Bảng đánh dấu sao sẽ được Ghin lên đầu", + "subscribe": "Đăng ký", "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "username": "Username", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", + "this-board": "bảng này", + "this-card": "thẻ này", + "spent-time-hours": "Đã dành thời gian (giờ)", + "overtime-hours": "Tăng ca (giờ)", + "overtime": "Tăng ca", + "has-overtime-cards": "Có thẻ làm thêm giờ", + "has-spenttime-cards": "Đã sử dụng thẻ thời gian", + "time": "Thời gian", + "title": "Tiêu đề", + "tracking": "Đang theo dõi", + "tracking-info": "Bạn sẽ được thông báo về bất kỳ thay đổi nào đối với những thẻ mà bạn tham gia với tư cách là người sáng tạo hoặc thành viên.", + "type": "Kiểu", + "unassign-member": "Bỏ chỉ định thành viên", + "unsaved-description": "Bạn có một mô tả chưa được lưu.", + "unwatch": "Bỏ theo dõi", + "upload": "Tải lên", + "upload-avatar": "Tải lên hình đại diện", + "uploaded-avatar": "Đã tải lên hình đại diện", + "custom-top-left-corner-logo-image-url": "Góc trên bên trái tùy chỉnh - URL hình ảnh Logo", + "custom-top-left-corner-logo-link-url": "Góc trên bên trái tùy chỉnh - URL liên kết Logo", + "custom-top-left-corner-logo-height": "Góc trên bên trái tùy chỉnh - Chiều cao Logo. Mặc định: 27", + "custom-login-logo-image-url": "Đăng nhập tùy chỉnh - URL hình ảnh Logo ", + "custom-login-logo-link-url": "Đăng nhập tùy chỉnh - Url liên kết Logo", + "text-below-custom-login-logo": "Đăng nhập tùy chỉnh - Văn bản bên dưới Logo", + "automatic-linked-url-schemes": "Lược đồ URL tùy chỉnh sẽ tự động có thể nhấp được. Một Lược đồ URL trên mỗi dòng", + "username": "Tài khoản", + "import-usernames": "Nhập khẩu Usernames", + "view-it": "Xem", + "warn-list-archived": "cảnh báo: thẻ này nằm trong một danh sách tại Lưu trữ", + "watch": "Theo dõi", + "watching": "Đang xem", + "watching-info": "Bạn sẽ được thông báo về bất kỳ thay đổi nào trong bảng này", "welcome-board": "Welcome Board", "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", + "welcome-list1": "Cơ bản", + "welcome-list2": "Nâng cao", + "card-templates-swimlane": "Mẫu Thẻ", + "list-templates-swimlane": "Mẫu Danh sách", + "board-templates-swimlane": "Mẫu Bảng", + "what-to-do": "Bạn muốn làm gì?", + "wipLimitErrorPopup-title": "Giới hạn WIP không hợp lệ", + "wipLimitErrorPopup-dialog-pt1": "Số lượng nhiệm vụ trong danh sách này cao hơn giới hạn WIP mà bạn đã xác định.", + "wipLimitErrorPopup-dialog-pt2": "Vui lòng chuyển một số nhiệm vụ ra khỏi danh sách này hoặc đặt giới hạn WIP cao hơn.", + "admin-panel": "Bảng quản trị", + "settings": "Cài đặt", + "people": "Mọi người", + "registration": "Đăng ký", + "disable-self-registration": "Vô hiệu hoá tự đăng ký", + "invite": "Mời", + "invite-people": "Mời mọi người", + "to-boards": "Đến bảng(s)", + "email-addresses": "Địa chỉ Email", + "smtp-host-description": "Địa chỉ của máy chủ SMTP xử lý email của bạn.", + "smtp-port-description": "Cổng mà máy chủ SMTP của bạn sử dụng cho các email gửi đi.", + "smtp-tls-description": "Bật hỗ trợ TLS cho máy chủ SMTP", "smtp-host": "SMTP Host", "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", + "smtp-username": "Tài khoản", + "smtp-password": "Mật khẩu", + "smtp-tls": "Hỗ trợ TLS", + "send-from": "Từ", + "send-smtp-test": "Gửi một email thử nghiệm cho chính bạn", + "invitation-code": "Mã lời mời", + "email-invite-register-subject": "__inviter__ đã gửi cho bạn lời mời", + "email-invite-register-text": "Gửi __user__,\n\n__inviter__ mời bạn tham gia bảng kanban để cộng tác.\n\nVui lòng truy cập liên kết bên dưới:\n__url__\n\nVà mã mời của bạn là: __icode__\n\nCảm ơn.", + "email-smtp-test-subject": "Email kiểm tra SMTP", + "email-smtp-test-text": "Bạn đã gửi thành công một email", + "error-invitation-code-not-exist": "Mã lời mời không tồn tại", + "error-notAuthorized": "Bạn không được phép xem trang này.", + "webhook-title": "Tên Webhook", + "webhook-token": "Mã thông báo (Tùy chọn để xác thực)", + "outgoing-webhooks": "Webhooks gửi đi", + "bidirectional-webhooks": "Webhooks hai chiều", + "outgoingWebhooksPopup-title": "Webhook gửi đi", + "boardCardTitlePopup-title": "Lọc tiêu đề thẻ", + "disable-webhook": "Vô hiệu hóa Webhook này", + "global-webhook": "Webhook toàn cầu", + "new-outgoing-webhook": "Webhook gửi đi mới", + "no-name": "(Không xác định)", + "Node_version": "Phiên bản Node", + "Meteor_version": "Phiên bản Meteor", + "MongoDB_version": "Phiên bản MongoDB", "MongoDB_storage_engine": "MongoDB storage engine", "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "createdAt": "Created at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "boardSubtaskSettingsPopup-title": "Board Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", + "OS_Cpus": "Số CPU", + "OS_Freemem": "Bộ nhớ trống", + "OS_Loadavg": "Tải trung bình", + "OS_Platform": "Nền tảng", + "OS_Release": "Phiên bản", + "OS_Totalmem": "Tổng bộ nhớ hệ điều hành", + "OS_Type": "Kiểu hệ điều hành", + "OS_Uptime": "Thời gian chạy", + "days": "ngày", + "hours": "giờ", + "minutes": "phút", + "seconds": "giây", + "show-field-on-card": "Hiển thị trường này trên thẻ", + "automatically-field-on-card": "Thêm trường vào thẻ mới", + "always-field-on-card": "Thêm trường vào tất cả các thẻ", + "showLabel-field-on-card": "Hiển thị nhãn trường trên minicard", + "yes": "Có", + "no": "Không", + "accounts": "Tài khoản", + "accounts-allowEmailChange": "Cho phép thay đổi email", + "accounts-allowUserNameChange": "Cho phép thay đổi tên người dùng", + "createdAt": "Được tạo lúc", + "modifiedAt": "Được sửa đổi lúc", + "verified": "Đã xác minh", + "active": "Hoạt động", + "card-received": "Đã nhận", + "card-received-on": "Nhận được trên", + "card-end": "Kết thúc", + "card-end-on": "Kết thúc vào", + "editCardReceivedDatePopup-title": "Thay đổi ngày nhận", + "editCardEndDatePopup-title": "Thay đổi ngày kết thúc", + "setCardColorPopup-title": "Đặt màu", + "setCardActionsColorPopup-title": "Chọn một màu", + "setSwimlaneColorPopup-title": "Chọn một màu", + "setListColorPopup-title": "Chọn một màu", + "assigned-by": "Được chỉ định bởi", + "requested-by": "Yêu cầu bởi", + "card-sorting-by-number": "Card sorting by number", + "board-delete-notice": "Việc xóa là vĩnh viễn. Bạn sẽ mất tất cả danh sách, thẻ và hành động liên quan đến bảng này.", + "delete-board-confirm-popup": "Tất cả danh sách, thẻ, nhãn và hoạt động sẽ bị xóa và bạn sẽ không thể khôi phục nội dung bảng. Không thể hoàn tác.", + "boardDeletePopup-title": "Xoá Bảng?", + "delete-board": "Xoá Bảng", + "default-subtasks-board": "Nhiệm vụ phụ cho __board__ bảng", + "default": "Mặc định", + "queue": "Hàng đợi", + "subtask-settings": "Cài đặt Nhiệm vụ phụ", + "card-settings": "Cài đặt Thẻ", + "boardSubtaskSettingsPopup-title": "Cài đặt Bảng Nhiệm vụ phụ", + "boardCardSettingsPopup-title": "Cài đặt thẻ", + "deposit-subtasks-board": "Gửi các nhiệm vụ phụ vào bảng này:", + "deposit-subtasks-list": "Danh sách đích cho các nhiệm vụ phụ được gửi tại đây:", + "show-parent-in-minicard": "Hiện cha mẹ trong minicard:", + "prefix-with-full-path": "Tiền tố với đường dẫn đầy đủ", + "prefix-with-parent": "Tiền tố với cha mẹ", + "subtext-with-full-path": "Văn bản phụ với đường dẫn đầy đủ", + "subtext-with-parent": "Văn bản phụ với cha mẹ", + "change-card-parent": "Thay đổi phụ huynh của thẻ", + "parent-card": "Thẻ cha", + "source-board": "Bảng nguồn", + "no-parent": "Không hiển thị cho cha mẹ", + "activity-added-label": "đã thêm nhãn '%s' vào %s", + "activity-removed-label": "đã xóa nhãn '%s' khỏi %s", + "activity-delete-attach": "đã xóa một tệp đính kèm khỏi %s", + "activity-added-label-card": "đã thêm nhãn '%s'", + "activity-removed-label-card": "đã xóa nhãn '%s'", + "activity-delete-attach-card": "đã xóa một tệp đính kèm", + "activity-set-customfield": "đặt trường tùy chỉnh '%s' thành '%s' trong %s", + "activity-unset-customfield": "bỏ đặt trường tùy chỉnh '%s' trong %s", + "r-rule": "Quy tắc", + "r-add-trigger": "Thêm trình kích hoạt", + "r-add-action": "Thêm hành động", + "r-board-rules": "Quy tắc bảng", + "r-add-rule": "Thêm quy tắc", + "r-view-rule": "Xem quy tắc", + "r-delete-rule": "Xoá quy tắc", + "r-new-rule-name": "Tiêu đề quy tắc mới", + "r-no-rules": "Không có quy tắc", + "r-trigger": "Kích hoạt", + "r-action": "Hoạt động", + "r-when-a-card": "Khi một thẻ", + "r-is": "là", + "r-is-moved": "được chuyển đi", + "r-added-to": "Đã thêm vào", + "r-removed-from": "Bị loại khỏi", + "r-the-board": "bảng", + "r-list": "danh sách", + "list": "Danh sách", + "set-filter": "Đặt bộ lọc", + "r-moved-to": "Chuyển đến", + "r-moved-from": "Đã di chuyển từ", + "r-archived": "Đã chuyển đến Lưu trữ", + "r-unarchived": "Đã khôi phục từ lưu trữ", + "r-a-card": "thẻ", + "r-when-a-label-is": "Khi một nhãn là", + "r-when-the-label": "Khi nhãn", + "r-list-name": "tên danh sách", + "r-when-a-member": "Khi một thành viên là", + "r-when-the-member": "Khi thành viên", + "r-name": "tên", + "r-when-a-attach": "Khi một tệp đính kèm", + "r-when-a-checklist": "Khi một checklist là", + "r-when-the-checklist": "Khi checklist", + "r-completed": "Đã hoàn thành", + "r-made-incomplete": "Làm chưa hoàn thành", + "r-when-a-item": "Khi một mục checklist là", + "r-when-the-item": "Khi mục checklist", + "r-checked": "Đã chọn", + "r-unchecked": "Đã bỏ chọn", + "r-move-card-to": "Di chuyển thẻ đến", + "r-top-of": "Trên cùng của", + "r-bottom-of": "Dưới cùng của", + "r-its-list": "danh sách của nó", + "r-archive": "Di chuyển đến Lưu trữ", + "r-unarchive": "Khôi phục từ Lưu trữ", + "r-card": "thẻ", "r-add": "Thêm", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", + "r-remove": "Xóa", + "r-label": "nhãn", + "r-member": "thành viên", + "r-remove-all": "Xóa tất cả thành viên khỏi thẻ", + "r-set-color": "Đặt màu cho", "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", + "r-check-all": "Đánh dấu tất cả", + "r-uncheck-all": "Bỏ đánh dấu tất cả", + "r-items-check": "mục của checklist", + "r-check": "Chọn", + "r-uncheck": "Bỏ chọn", + "r-item": "mục", + "r-of-checklist": "của checklist", + "r-send-email": "Gửi email", + "r-to": "đến", + "r-of": "của", + "r-subject": "Chủ đề", + "r-rule-details": "Chi tiết quy tắc", + "r-d-move-to-top-gen": "Di chuyển thẻ lên đầu danh sách của nó", + "r-d-move-to-top-spec": "Di chuyển thẻ lên đầu của danh sách", + "r-d-move-to-bottom-gen": "Di chuyển thẻ xuống cuối danh sách của nó", + "r-d-move-to-bottom-spec": "Di chuyển thẻ xuống cuối của danh sách", + "r-d-send-email": "Gửi email", + "r-d-send-email-to": "đến", + "r-d-send-email-subject": "Chủ đề", + "r-d-send-email-message": "tin nhắn", + "r-d-archive": "Di chuyển thẻ vào Lưu trữ", + "r-d-unarchive": "Khôi phục thẻ từ Lưu trữ", + "r-d-add-label": "Thêm nhãn", + "r-d-remove-label": "Xóa nhãn", + "r-create-card": "Tạo thẻ mới", + "r-in-list": "trong danh sách", + "r-in-swimlane": "trong làn ngang", + "r-d-add-member": "Thêm thành viên", + "r-d-remove-member": "Xóa thành viên", + "r-d-remove-all-member": "Xóa tất cả thành viên", + "r-d-check-all": "Check tất cả các mục của một danh sách", + "r-d-uncheck-all": "Bỏ check tất cả các mục của danh sách", + "r-d-check-one": "Chọn mục", + "r-d-uncheck-one": "Bỏ chọn mục", + "r-d-check-of-list": "của checklist", + "r-d-add-checklist": "Thêm checklist", + "r-d-remove-checklist": "Xóa checklist", + "r-by": "bởi", + "r-add-checklist": "Thêm checklist", + "r-with-items": "với các mục", "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value.", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", + "r-add-swimlane": "Thêm làn ngang", + "r-swimlane-name": "tên làn ngang", + "r-board-note": "Lưu ý: để trống một trường để khớp với mọi giá trị có thể.", + "r-checklist-note": "Lưu ý: các mục của danh sách kiểm tra phải được viết dưới dạng các giá trị được phân tách bằng dấu phẩy.", + "r-when-a-card-is-moved": "Khi một thẻ được chuyển sang một danh sách khác", + "r-set": "Đặt", + "r-update": "Cập nhật", + "r-datefield": "trường ngày tháng", + "r-df-start-at": "bắt đầu", + "r-df-due-at": "đến hạn", + "r-df-end-at": "kết thúc", + "r-df-received-at": "đã nhận", + "r-to-current-datetime": "đến ngày/giờ hiện tại", + "r-remove-value-from": "Xóa giá trị khỏi", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "layout": "Layout", - "hide-logo": "Hide Logo", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "people-number": "The number of people is:", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "new": "New", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "authentication-method": "Phương thức xác thực", + "authentication-type": "Loại xác thực", + "custom-product-name": "Tên sản phẩm tùy chỉnh", + "layout": "Bố cục", + "hide-logo": "Ẩn Logo", + "add-custom-html-after-body-start": "Thêm HTML tùy chỉnh sau thẻ <body>", + "add-custom-html-before-body-end": "Thêm HTML tùy chỉnh trước thẻ </body>", + "error-undefined": "Đã xảy ra sự cố", + "error-ldap-login": "Đã xảy ra lỗi khi cố gắng đăng nhập", + "display-authentication-method": "Hiển thị phương pháp xác thực", + "default-authentication-method": "Phương thức xác thực mặc định", + "duplicate-board": "Bảng trùng lặp", + "org-number": "Số lượng các tổ chức là:", + "team-number": "Số lượng các đội là:", + "people-number": "Số người là:", + "swimlaneDeletePopup-title": "Xóa Swimlane?", + "swimlane-delete-pop": "Tất cả các hành động sẽ bị xóa khỏi nguồn cấp dữ liệu hoạt động và bạn sẽ không thể khôi phục swimlane. Không có hoàn tác.", + "restore-all": "Khôi phục lại tất cả", + "delete-all": "Xoá tất cả", + "loading": "Đang tải, vui lòng đợi.", + "previous_as": "lần trước là", + "act-a-dueAt": "sửa đổi thời gian đến hạn \nKhi nào: __timeValue__\nỞ đâu: __card__\n đến hạn trước đó là __timeOldValue__", + "act-a-endAt": "đã sửa đổi thời gian kết thúc thành __timeValue__ từ (__timeOldValue__)", + "act-a-startAt": "sửa đổi thời gian bắt đầu thành __timeValue__ from (__timeOldValue__)", + "act-a-receivedAt": "đã sửa đổi thời gian nhận thành __timeValue__ từ (__timeOldValue__)", + "a-dueAt": "sửa đổi thời gian đến hạn thành", + "a-endAt": "đã sửa đổi thời gian kết thúc thành", + "a-startAt": "thời gian bắt đầu được sửa đổi thành", + "a-receivedAt": "đã sửa đổi thời gian nhận được", + "almostdue": "thời gian đến hạn hiện tại %s đang đến gần", + "pastdue": "thời gian đến hạn hiện tại %s đã qua", + "duenow": "giờ đến hạn hiện tại %s là hôm nay", + "act-newDue": "__list__/__card__ có lời nhắc đến hạn lần 1 [__board__]", + "act-withDue": "__list__/__card__ lời nhắc đến hạn [__board__]", + "act-almostdue": "đang nhắc nhở thời hạn hiện tại là (__timeValue__) của __card__ đang đến gần", + "act-pastdue": "đang nhắc nhở thời hạn hiện tại (__timeValue__) của __card__ đã qua", + "act-duenow": "đã được nhắc nhở hiện tại đến hạn (__timeValue__) của __card__ bây giờ là", + "act-atUserComment": "Bạn đã được đề cập trong [__board__] __list__/__card__", + "delete-user-confirm-popup": "Bạn có chắc chắn muốn xóa tài khoản này không? Không thể hoàn tác.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", + "accounts-allowUserDelete": "Cho phép người dùng tự xóa tài khoản của họ", + "hide-minicard-label-text": "Ẩn nhãn minicard", + "show-desktop-drag-handles": "Hiển thị nút kéo thả trên Desktop", + "assignee": "Người được giao", + "cardAssigneesPopup-title": "Người được giao", + "addmore-detail": "Thêm mô tả chi tiết hơn", + "show-on-card": "Hiển thị trên thẻ", + "new": "Mới", + "editOrgPopup-title": "Sửa Cơ quan", + "newOrgPopup-title": "Tổ chức mới", + "editTeamPopup-title": "Sửa Nhóm", + "newTeamPopup-title": "Team Mới", + "editUserPopup-title": "Sửa Người dùng", + "newUserPopup-title": "Người dùng mới", + "notifications": "Thông báo", + "view-all": "Xem Tất cả", + "filter-by-unread": "Lọc theo Chưa đọc", + "mark-all-as-read": "Đánh dấu tất cả là đã đọc", + "remove-all-read": "Xóa tất cả đã đọc", + "allow-rename": "Cho phép đổi tên", + "allowRenamePopup-title": "Cho phép đổi tên", + "start-day-of-week": "Đặt ngày bắt đầu trong tuần", + "monday": "Thứ hai", + "tuesday": "Thứ ba", + "wednesday": "Thứ tư", + "thursday": "Thứ năm", + "friday": "Thứ sáu", + "saturday": "Thứ bảy", + "sunday": "Chủ nhật", + "status": "Trạng thái", + "swimlane": "Làn ngang", + "owner": "Chủ sở hữu", + "last-modified-at": "Sửa đổi lần cuối lúc", + "last-activity": "Hoạt động cuối", + "voting": "Bình chọn", + "archived": "Đã lưu trữ", + "delete-linked-card-before-this-card": "Bạn không thể xóa thẻ này trước khi xóa lần đầu tiên thẻ được liên kết có", + "delete-linked-cards-before-this-list": "Bạn không thể xóa danh sách này trước khi xóa lần đầu tiên các thẻ được liên kết trỏ đến các thẻ trong danh sách này", + "hide-checked-items": "Ẩn đã hoàn thành", + "task": "Nhiệm vụ", + "create-task": "Tạo Nhiệm Vụ", + "ok": "OK", + "organizations": "Các tổ chức", + "teams": "Teams", + "displayName": "Tên Hiển Thị", + "shortName": "Tên ngắn", + "website": "Website", + "person": "Cá nhân", + "my-cards": "Thẻ của tôi", + "card": "Thẻ", + "board": "Bảng", + "context-separator": "/", + "myCardsSortChange-title": "Sắp xếp thẻ của tôi", + "myCardsSortChangePopup-title": "Sắp xếp thẻ của tôi", + "myCardsSortChange-choice-board": "Theo bảng", + "myCardsSortChange-choice-dueat": "Theo Ngày đến hạn", + "dueCards-title": "Thẻ đến hạn", + "dueCardsViewChange-title": "Xem Thẻ đến hạn", + "dueCardsViewChangePopup-title": "Xem Thẻ đến hạn", + "dueCardsViewChange-choice-me": "Tôi", + "dueCardsViewChange-choice-all": "Tất cả người dùng", + "dueCardsViewChange-choice-all-description": "Hiển thị tất cả các thẻ chưa hoàn thành có ngày *Đến hạn* từ bảng mà người dùng có quyền.", + "broken-cards": "Thẻ bị hỏng", + "board-title-not-found": "Không tìm thấy bảng '%s'", + "swimlane-title-not-found": "Không tìm thấy làn ngang '%s'", + "list-title-not-found": "Không tìm thấy danh sách '%s'", + "label-not-found": "Không tìm thấy nhãn '%s'", + "label-color-not-found": "Không tìm thấy màu nhãn %s", + "user-username-not-found": "Không tìm thấy tên người dùng '%s'", + "comment-not-found": "Không tìm thấy thẻ có bình luận chứa văn bản '%s'", + "globalSearch-title": "Tìm tất cả các Bảng", + "no-cards-found": "Không tìm thấy thẻ", + "one-card-found": "Đã tìm thấy một thẻ", + "n-cards-found": "Đã tìm thấy %s thẻ", + "n-n-of-n-cards-found": "__start__-__end__ của __total__ Thẻ được Tìm thấy", + "operator-board": "bảng", + "operator-board-abbrev": "b", + "operator-swimlane": "làn ngang", + "operator-swimlane-abbrev": "s", + "operator-list": "danh sách", + "operator-list-abbrev": "l", + "operator-label": "nhãn", + "operator-label-abbrev": "#", + "operator-user": "người dùng", + "operator-user-abbrev": "@", + "operator-member": "thành viên", + "operator-member-abbrev": "m", + "operator-assignee": "người được giao", + "operator-assignee-abbrev": "a", + "operator-creator": "người tạo", + "operator-status": "trạng thái", + "operator-due": "đến hạn", + "operator-created": "đã tạo", + "operator-modified": "sửa đổi", + "operator-sort": "sắp xếp", + "operator-comment": "bình luận", + "operator-has": "có", + "operator-limit": "giới hạn", + "predicate-archived": "được lưu trữ", + "predicate-open": "mở", + "predicate-ended": "đã kết thúc", + "predicate-all": "tất cả", + "predicate-overdue": "quá hạn", + "predicate-week": "tuần", + "predicate-month": "tháng", + "predicate-quarter": "quý", + "predicate-year": "năm", + "predicate-due": "đến hạn", + "predicate-modified": "sửa đổi", + "predicate-created": "đã tạo", + "predicate-attachment": "tập đính kèm", + "predicate-description": "mô tả", + "predicate-checklist": "checklist", + "predicate-start": "bắt đầu", + "predicate-end": "kết thúc", + "predicate-assignee": "người được giao", + "predicate-member": "thành viên", + "predicate-public": "công khai", + "predicate-private": "riêng tư", + "operator-unknown-error": "%s không phải là một toán tử", + "operator-number-expected": "toán tử __operator__ chấp nhận một số, có giá trị '__value__'", + "operator-sort-invalid": "sắp xếp của '%s' không hợp lệ", + "operator-status-invalid": "'%s' không phải là trạng thái hợp lệ", + "operator-has-invalid": "%s không phải là một kiểm tra sự tồn tại hợp lệ", + "operator-limit-invalid": "%s không phải là giới hạn hợp lệ. Giới hạn phải là một số nguyên dương.", + "next-page": "Trang tiếp theo", + "previous-page": "Trang trước", + "heading-notes": "Ghi chú", + "globalSearch-instructions-heading": "Hướng dẫn Tìm kiếm", + "globalSearch-instructions-description": "Tìm kiếm có thể bao gồm các toán tử để tinh chỉnh tìm kiếm. Toán tử được chỉ định bằng cách viết tên toán tử và giá trị được phân tách bằng dấu hai chấm. Ví dụ, một đặc tả toán tử của `list:Blocked` sẽ giới hạn tìm kiếm đối với các thẻ có trong danh sách có tên là *Blocked*. Nếu giá trị chứa khoảng trắng hoặc ký tự đặc biệt, nó phải được đặt trong dấu ngoặc kép (ví dụ. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Các toán tử có sẵn:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - thẻ trong bảng phù hợp với *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - thẻ trong danh sách phù hợp với *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - thẻ trong làn ngang phù hợp với *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - thẻ có nhận xét chứa *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - thẻ có nhãn phù hợp *<color>* hoặc *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - viết tắt cho `__operator_label__:<color>` hoặc `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - thẻ trong đó *<username>* là *thành viên* hoặc *người được giao*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - viết tắt cho `người dùng:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - thẻ trong đó *<username>* là *thành viên*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - thẻ trong đó *<username>* là *người được giao*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - thẻ trong đó *<username>* là người tạo thẻ", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - thẻ có thời hạn lên đến *<n>* ngày kể từ bây giờ. `__operator_due__:__predicate_overdue__ liệt kê tất cả các thẻ đã quá hạn sử dụng.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - thẻ đã được tạo *<n>* ngày trước hoặc ít hơn", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - thẻ đã được sửa đổi *<n>* ngày trước hoặc ít hơn", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - trong đó *<status>* là một trong các giá trị sau:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - thẻ đã lưu trữ", + "globalSearch-instructions-status-all": "`__predicate_all__` - tất cả các thẻ lưu trữ và hủy lưu trữ", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - thẻ với ngày kết thúc", + "globalSearch-instructions-status-public": "`__predicate_public__` - chỉ thẻ trong bảng công khai", + "globalSearch-instructions-status-private": "`__predicate_private__` - chỉ thẻ trong bảng riêng tư", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - trong đó *<field>* là một trong `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` hoặc `__predicate_member__`. Đặt dấu `-` trước *<field>* sẽ tìm kiếm sự không có giá trị trong trường đó (ví dụ. `has:-due` tìm kiếm các thẻ không có ngày đến hạn).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - trong đó *<sort-name>* là một trong `__predicate_due__`, `__predicate_created__` hoặc `__predicate_modified__`. Đối với sắp xếp giảm dần, hãy đặt dấu `-` trước tên sắp xếp.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - trong đó *<n>* là một số nguyên dương thể hiện số lượng thẻ được hiển thị trên mỗi trang.", + "globalSearch-instructions-notes-1": "Nhiều toán tử có thể được chỉ định.", + "globalSearch-instructions-notes-2": "Các toán tử tương tự là *OR* cùng nhau. Các thẻ phù hợp với bất kỳ điều kiện nào sẽ được trả lại.\n`__operator_list__:Available __operator_list__:Blocked` sẽ trả lại các thẻ có trong bất kỳ danh sách nào có tên *Blocked* hoặc *Available*.", + "globalSearch-instructions-notes-3": "Các toán tử khác nhau là *AND* kết hợp với nhau. Chỉ những thẻ phù hợp với tất cả các toán tử khác nhau mới được trả lại. `__operator_list__:Available __operator_label__:red` chỉ trả về các thẻ trong danh sách *Có sẵn* với nhãn *red*.", + "globalSearch-instructions-notes-3-2": "Ngày có thể được chỉ định dưới dạng số nguyên dương hoặc âm hoặc sử dụng `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` hoặc `__predicate_year__` cho giai đoạn hiện tại.", + "globalSearch-instructions-notes-4": "Tìm kiếm văn bản không phân biệt chữ hoa chữ thường.", + "globalSearch-instructions-notes-5": "Theo mặc định, thẻ đã lưu trữ không được tìm kiếm.", + "link-to-search": "Liên kết đến tìm kiếm này", + "excel-font": "Arial", + "number": "Số", + "label-colors": "Màu Nhãn", + "label-names": "Tên Nhãn", + "archived-at": "được lưu trữ tại", + "sort-cards": "Sắp xếp thẻ", + "cardsSortPopup-title": "Sắp xếp thẻ", + "due-date": "Ngày đến hạn", + "server-error": "Lỗi máy chủ", + "server-error-troubleshooting": "Vui lòng gửi lỗi do máy chủ tạo ra.\nĐể cài đặt nhanh, hãy chạy: `sudo snap logs wekan.wekan`\nĐể cài đặt Docker, hãy chạy: `sudo docker logs wekan-app`", + "title-alphabetically": "Tiêu đề (theo thứ tự bảng chữ cái)", + "created-at-newest-first": "Được tạo lúc (Mới nhất đầu tiên)", + "created-at-oldest-first": "Được tạo lúc (Cũ nhất trước)", + "links-heading": "Liên kết", + "hide-system-messages-of-all-users": "Ẩn thông báo hệ thống của tất cả người dùng", + "now-system-messages-of-all-users-are-hidden": "Bây giờ thông báo hệ thống của tất cả người dùng bị ẩn", + "move-swimlane": "Di chuyển Làn ngang", + "moveSwimlanePopup-title": "Di chuyển Làn ngang", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Định dạng (sử dụng %{value} làm trình giữ chỗ)", + "custom-field-stringtemplate-separator": "Dấu phân cách (sử dụng hoặc   cho một khoảng trắng)", + "custom-field-stringtemplate-item-placeholder": "Nhấn enter để thêm các mục khác", + "creator": "Người tạo", + "filesReportTitle": "Tệp báo cáo", + "orphanedFilesReportTitle": "Tệp báo cáo mồ côi", + "reports": "Báo cáo", + "rulesReportTitle": "Quy tắc Báo cáo", + "copy-swimlane": "Sao chép Làn ngang", + "copySwimlanePopup-title": "Sao chép Làn ngang", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index 4a487979c..c49dfedea 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "未完成清单 %s", "activity-editComment": "评论已编辑", "activity-deleteComment": "评论已删除", + "activity-receivedDate": "已将接收日期从 %s 修改为 %s", + "activity-startDate": "已将开始日期从 %s 修改为 %s", + "activity-dueDate": "已将到期日期从 %s 修改为 %s", + "activity-endDate": "已将结束日期从 %s 修改为 %s", "add-attachment": "添加附件", "add-board": "添加看板", + "add-template": "Add Template", "add-card": "添加卡片", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "添加泳道图", "add-subtask": "添加子任务", "add-checklist": "添加待办清单", @@ -113,6 +120,8 @@ "archives": "归档", "template": "模板", "templates": "模板", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "指派成员", "attached": "附加", "attachment": "附件", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "删除附件?", "attachments": "附件", "auto-watch": "自动关注新建的看板", - "avatar-too-big": "头像过大 (上限 70 KB)", + "avatar-too-big": "头像过大 (上限 520 KB)", "back": "返回", "board-change-color": "更改颜色", "board-nb-stars": "%s 星标", "board-not-found": "看板不存在", "board-private-info": "该看板将被设为 <strong>私有</strong>.", "board-public-info": "该看板将被设为 <strong>公开</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "修改看板背景", "boardChangeTitlePopup-title": "重命名看板", "boardChangeVisibilityPopup-title": "更改可视级别", @@ -138,6 +148,7 @@ "board-view-cal": "日历", "board-view-swimlanes": "泳道图", "board-view-collapse": "崩溃", + "board-view-gantt": "甘特", "board-view-lists": "列表", "bucket-example": "例如 “目标清单”", "cancel": "取消", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "附件来源", "cardCustomField-datePopup-title": "修改日期", "cardCustomFieldsPopup-title": "编辑自定义字段", + "cardStartVotingPopup-title": "建立投票", + "positiveVoteMembersPopup-title": "支持", + "negativeVoteMembersPopup-title": "反对", + "card-edit-voting": "编辑投票", + "editVoteEndDatePopup-title": "修改投票截止日期", + "allowNonBoardMembers": "允许所有已登入账户", + "vote-question": "投票题目", + "vote-public": "查看投票结果", + "vote-for-it": "同意", + "vote-against": "反对", + "deleteVotePopup-title": "删除投票?", + "vote-delete-pop": "永久删除。此操作会将所有数据删除掉。", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "彻底删除卡片?", "cardDetailsActionsPopup-title": "卡片操作", "cardLabelsPopup-title": "标签", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "新建模板", "cards": "卡片", "cards-count": "卡片", + "cards-count-one": "卡片", "casSignIn": "用CAS登录", "cardType-card": "卡片", "cardType-linkedCard": "已链接卡片", @@ -191,6 +236,7 @@ "close": "关闭", "close-board": "关闭看板", "close-board-pop": "您可以通过主页头部的“归档”按钮,来恢复看板。", + "close-card": "Close Card", "color-black": "黑色", "color-blue": "蓝色", "color-crimson": "深红", @@ -244,6 +290,8 @@ "current": "当前", "custom-field-delete-pop": "没有撤销,此动作将从所有卡片中移除自定义字段并销毁历史。", "custom-field-checkbox": "选择框", + "custom-field-currency": "货币", + "custom-field-currency-option": "货币代码", "custom-field-date": "日期", "custom-field-dropdown": "下拉列表", "custom-field-dropdown-none": "(无)", @@ -297,13 +345,27 @@ "error-board-notAMember": "需要成为看板成员才能执行此操作", "error-json-malformed": "文本不是合法的 JSON", "error-json-schema": "JSON 数据没有用正确的格式包含合适的信息", + "error-csv-schema": "CSV(逗号分隔值)/TSV(制表符分隔值)数据格式的不正确", "error-list-doesNotExist": "不存在此列表", "error-user-doesNotExist": "该用户不存在", "error-user-notAllowSelf": "无法邀请自己", "error-user-notCreated": "该用户未能成功创建", "error-username-taken": "此用户名已存在", + "error-orgname-taken": "此组织名称已被使用", + "error-teamname-taken": "此团队名称已被使用", "error-email-taken": "此EMail已存在", "export-board": "导出看板", + "export-board-json": "看板导出为JSON", + "export-board-csv": "看板导出为CSV", + "export-board-tsv": "看板导出为TSV", + "export-board-excel": "看板导出为Excel", + "user-can-not-export-excel": "用户无法导出Excel", + "export-board-html": "看板导出为HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "导出看板", + "exportCardPopup-title": "Export card", "sort": "排序", "sort-desc": "点此来将列表排序", "list-sort-by": "按此来将列表排序:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "过滤", "filter-cards": "过滤卡片或列表", + "filter-dates-label": "按日期过滤", + "filter-no-due-date": "无截止日期", + "filter-overdue": "逾期", + "filter-due-today": "今天到期", + "filter-due-this-week": "这周到期", + "filter-due-tomorrow": "明天到期", "list-filter-label": "以标题过滤列表", "filter-clear": "清空过滤器", + "filter-labels-label": "根据标签过滤", "filter-no-label": "无标签", + "filter-member-label": "按成员过滤", "filter-no-member": "无成员", + "filter-assignee-label": "按指定人过滤", + "filter-no-assignee": "没有代理人", + "filter-custom-fields-label": "按自定义字段过滤", "filter-no-custom-fields": "无自定义字段", "filter-show-archive": "显示归档的列表", "filter-hide-empty": "隐藏空列表", "filter-on": "过滤器启用", "filter-on-desc": "你正在过滤该看板上的卡片,点此编辑过滤。", "filter-to-selection": "要选择的过滤器", + "other-filters-label": "其他过滤", "advanced-filter-label": "高级过滤器", "advanced-filter-description": "高级过滤器可以使用包含如下操作符的字符串进行过滤:== != <= >= && || ( ) 。操作符之间用空格隔开。输入字段名和数值就可以过滤所有自定义字段。例如:Field1 == Value1。注意如果字段名或数值包含空格,需要用单引号。例如: 'Field 1' == 'Value 1'。要跳过单个控制字符(' \\/),请使用 \\ 转义字符。例如: Field1 = I\\'m。支持组合使用多个条件,例如: F1 == V1 || F1 == V2。通常以从左到右的顺序进行判断。可以通过括号修改顺序,例如:F1 == V1 && ( F2 == V2 || F2 == V3 )。也支持使用正则表达式搜索文本字段。", "fullname": "全称", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "创建看板", "home": "首页", "import": "导入", + "impersonate-user": "模拟用户", "link": "链接", "import-board": "导入看板", "import-board-c": "导入看板", "import-board-title-trello": "从Trello导入看板", "import-board-title-wekan": "从以前的导出数据导入看板", - "import-sandstorm-backup-warning": "在检查此颗粒是否关闭和再次打开之前,不要删除从原始导出的看板或Trello导入的数据,否则看板会发生未知的错误,这将意味着数据丢失。", - "import-sandstorm-warning": "导入的面板将删除所有已存在于面板上的数据并替换他们为导入的面板。", + "import-board-title-csv": "导入CSV/TSV看板", "from-trello": "自 Trello", "from-wekan": "自以前的导出", + "from-csv": "从CSV/TSV", "import-board-instruction-trello": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本", + "import-board-instruction-csv": "粘贴逗号分隔值(CSV)/制表符分隔值(TSV)。", "import-board-instruction-wekan": "在您的看板,点击“菜单”,然后“导出看板”,复制下载文件中的文本。", "import-board-instruction-about-errors": "如果在导入看板时出现错误,导入工作可能仍然在进行中,并且看板已经出现在全部看板页。", "import-json-placeholder": "粘贴您有效的 JSON 数据至此", + "import-csv-placeholder": "在此处粘贴有效的CSV/TSV数据", "import-map-members": "映射成员", "import-members-map": "您导入的看板有一些成员,请映射这些成员到您导入的用户。", + "import-members-map-note": "提示:未指定用户将被指派给当前用户。", "import-show-user-mapping": "核对成员映射", "import-user-select": "为这个成员选择您已经存在的用户", "importMapMembersAddPopup-title": "选择成员", @@ -360,7 +438,7 @@ "just-invited": "您刚刚被邀请加入此看板", "keyboard-shortcuts": "键盘快捷键", "label-create": "创建标签", - "label-default": "%s 标签 (默认)", + "label-default": "%s 标签(默认)", "label-delete-pop": "此操作不可逆,这将会删除该标签并清除它的历史记录。", "labels": "标签", "language": "语言", @@ -375,9 +453,13 @@ "list-select-cards": "选择列表中的所有卡片", "set-color-list": "设置颜色", "listActionPopup-title": "列表操作", + "settingsUserPopup-title": "用户设置", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "泳道图操作", "swimlaneAddPopup-title": "在下面添加一个泳道", "listImportCardPopup-title": "导入 Trello 卡片", + "listImportCardsTsvPopup-title": "导入Excel CSV/TSV", "listMorePopup-title": "更多", "link-list": "关联到这个列表", "list-delete-pop": "所有活动将被从活动动态中删除并且你无法恢复他们,此操作无法撤销。", @@ -396,6 +478,8 @@ "moveCardToTop-title": "移动至顶端", "moveSelectionPopup-title": "移动选择", "multi-selection": "多选", + "multi-selection-label": "设置标签", + "multi-selection-member": "选择成员", "multi-selection-on": "多选启用", "muted": "静默", "muted-info": "你将不会收到此看板的任何变更通知", @@ -440,9 +524,10 @@ "save": "保存", "search": "搜索", "rules": "规则", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "搜索", + "search-cards": "搜寻看板內的卡片/列表标题、描述、自定义字段", + "search-example": "输入查询信息后按回车", "select-color": "选择颜色", + "select-board": "选择看板", "set-wip-limit-value": "设置此列表中的最大任务数", "setWipLimitPopup-title": "设置最大任务数", "shortcut-assign-self": "指派当前卡片给自己", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "过滤我的卡片", "shortcut-show-shortcuts": "显示此快捷键列表", "shortcut-toggle-filterbar": "切换过滤器边栏", + "shortcut-toggle-searchbar": "切换查询边栏", "shortcut-toggle-sidebar": "切换面板边栏", "show-cards-minimum-count": "当列表中的卡片多于此阈值时将显示数量", "sidebar-open": "打开侧栏", @@ -481,7 +567,15 @@ "upload": "上传", "upload-avatar": "上传头像", "uploaded-avatar": "头像已经上传", + "custom-top-left-corner-logo-image-url": "通过图片链接设置左上角图标", + "custom-top-left-corner-logo-link-url": "设置左上角图标链接地址", + "custom-top-left-corner-logo-height": "设置左上角图标高度。默认值:27", + "custom-login-logo-image-url": "设置登录图标链接地址", + "custom-login-logo-link-url": "设置登录图标链接", + "text-below-custom-login-logo": "设置登录图标下方文字", + "automatic-linked-url-schemes": "自定义URL模式下URL链接默认为可点击。每行仅限一种URL模式。", "username": "用户名", + "import-usernames": "导入用户名", "view-it": "查看", "warn-list-archived": "警告:此卡片在列表归档中", "watch": "关注", @@ -553,7 +647,8 @@ "minutes": "分钟", "seconds": "秒", "show-field-on-card": "在卡片上显示此字段", - "automatically-field-on-card": "自动创建所有卡片的字段", + "automatically-field-on-card": "添加字段至新卡片", + "always-field-on-card": "添加字段至所有卡片", "showLabel-field-on-card": "在迷你卡片上显示字段标签", "yes": "是", "no": "否", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "允许邮箱变更", "accounts-allowUserNameChange": "允许变更用户名", "createdAt": "创建于", + "modifiedAt": "修改时间", "verified": "已验证", "active": "活跃", "card-received": "已接收", @@ -575,12 +671,13 @@ "setListColorPopup-title": "选择一种颜色", "assigned-by": "指派人", "requested-by": "需求人", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "删除时永久操作,将会丢失此看板上的所有列表、卡片和动作。", "delete-board-confirm-popup": "所有列表、卡片、标签和活动都回被删除,将无法恢复看板内容。不支持撤销。", "boardDeletePopup-title": "删除看板?", "delete-board": "删除看板", "default-subtasks-board": "__board__ 看板的子任务", - "default": "缺省", + "default": "默认", "queue": "队列", "subtask-settings": "子任务设置", "card-settings": "卡片设置", @@ -614,6 +711,8 @@ "r-delete-rule": "删除规则", "r-new-rule-name": "新建规则标题", "r-no-rules": "暂无规则", + "r-trigger": "触发器", + "r-action": "操作", "r-when-a-card": "当一张卡片", "r-is": "是", "r-is-moved": "已经移动", @@ -621,6 +720,7 @@ "r-removed-from": "已移除", "r-the-board": "该看板", "r-list": "列表", + "list": "列表", "set-filter": "设置过滤器", "r-moved-to": "移至", "r-moved-from": "已移动", @@ -665,6 +765,7 @@ "r-of-checklist": "清单的", "r-send-email": "发送邮件", "r-to": "收件人", + "r-of": "分之", "r-subject": "标题", "r-rule-details": "规则详情", "r-d-move-to-top-gen": "移动卡片到其列表顶部", @@ -717,14 +818,16 @@ "authentication-type": "认证类型", "custom-product-name": "自定义产品名称", "layout": "布局", - "hide-logo": "隐藏LOGO", + "hide-logo": "隐藏图标", "add-custom-html-after-body-start": "添加定制的HTML在开始<body>之前", "add-custom-html-before-body-end": "添加定制的HTML在结束</body>之后", "error-undefined": "出了点问题", - "error-ldap-login": "尝试登陆时出错", + "error-ldap-login": "尝试登录时出错", "display-authentication-method": "显示认证方式", - "default-authentication-method": "缺省认证方式", + "default-authentication-method": "默认认证方式", "duplicate-board": "复制看板", + "org-number": "组织数量为:", + "team-number": "团队数量为:", "people-number": "人数是:", "swimlaneDeletePopup-title": "是否删除泳道?", "swimlane-delete-pop": "所有活动将从活动源中删除,您将无法恢复泳道。此操作无法撤销。", @@ -750,6 +853,8 @@ "act-duenow": "__card__ 的当前到期提醒(__timeValue__) 现在到期", "act-atUserComment": "[__board__] __list__/__card__ 提到了您", "delete-user-confirm-popup": "确实要删除此帐户吗?此操作无法撤销。", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "允许用户自行删除其帐户", "hide-minicard-label-text": "隐藏迷你卡片标签文本", "show-desktop-drag-handles": "显示桌面拖放手柄", @@ -758,12 +863,200 @@ "addmore-detail": "添加更详细的说明", "show-on-card": "显示卡片", "new": "新", + "editOrgPopup-title": "编辑组织", + "newOrgPopup-title": "创建组织", + "editTeamPopup-title": "编辑团队", + "newTeamPopup-title": "创建团队", "editUserPopup-title": "修改用户", - "newUserPopup-title": "新用户", + "newUserPopup-title": "新增用户", "notifications": "提示", "view-all": "查看全部", "filter-by-unread": "过滤未读", "mark-all-as-read": "标记全部已读", + "remove-all-read": "移除所有已读", "allow-rename": "允许重命名", - "allowRenamePopup-title": "允许重命名" + "allowRenamePopup-title": "允许重命名", + "start-day-of-week": "设定一周的开始", + "monday": "周一", + "tuesday": "周二", + "wednesday": "周三", + "thursday": "周四", + "friday": "周五", + "saturday": "周六", + "sunday": "周日", + "status": "状态", + "swimlane": "泳道", + "owner": "所有者", + "last-modified-at": "上次修改时间", + "last-activity": "上次活动", + "voting": "投票", + "archived": "存档", + "delete-linked-card-before-this-card": "在你首次删除卡片前你无法删除此选项卡片", + "delete-linked-cards-before-this-list": "在首先删除指向此列表中的卡的链接卡之前,不能删除此列表", + "hide-checked-items": "隐藏选中项", + "task": "任务", + "create-task": "创建任务", + "ok": "确认", + "organizations": "组织", + "teams": "团队", + "displayName": "显示名称", + "shortName": "简称", + "website": "网站", + "person": "人员", + "my-cards": "我的卡片", + "card": "卡片", + "board": "看板", + "context-separator": "/", + "myCardsSortChange-title": "我的卡片排序", + "myCardsSortChangePopup-title": "我的卡片排序", + "myCardsSortChange-choice-board": "根据看板", + "myCardsSortChange-choice-dueat": "根据截至日期", + "dueCards-title": "逾期的卡片", + "dueCardsViewChange-title": "逾期卡片视图", + "dueCardsViewChangePopup-title": "逾期卡片视图", + "dueCardsViewChange-choice-me": "我", + "dueCardsViewChange-choice-all": "所有用户", + "dueCardsViewChange-choice-all-description": "根据截至日期显示用户有权限访问的看板中未完成的卡片。", + "broken-cards": "损坏的卡片", + "board-title-not-found": "无法找到看板 '%s'", + "swimlane-title-not-found": "找不到泳道 %s", + "list-title-not-found": "无法找到列表%s", + "label-not-found": "无法找到标签%s", + "label-color-not-found": "无法找到标签颜色%s", + "user-username-not-found": "用户名%s无法找到", + "comment-not-found": "找不到评论包含%s的卡片。", + "globalSearch-title": "查询所有看板", + "no-cards-found": "无法查询到卡片", + "one-card-found": "查询到一个卡片", + "n-cards-found": "查询到%s个卡片", + "n-n-of-n-cards-found": "__start__-__end__ 共 __total__ 卡片被找到", + "operator-board": "看板", + "operator-board-abbrev": "b", + "operator-swimlane": "泳道", + "operator-swimlane-abbrev": "s", + "operator-list": "列表", + "operator-list-abbrev": "l", + "operator-label": "标签", + "operator-label-abbrev": "#", + "operator-user": "用户", + "operator-user-abbrev": "@", + "operator-member": "成员", + "operator-member-abbrev": "m", + "operator-assignee": "指定人", + "operator-assignee-abbrev": "a", + "operator-creator": "创建者", + "operator-status": "状态", + "operator-due": "至", + "operator-created": "已创建", + "operator-modified": "已编辑", + "operator-sort": "排序", + "operator-comment": "评论", + "operator-has": "已有", + "operator-limit": "限制", + "predicate-archived": "存档", + "predicate-open": "打开", + "predicate-ended": "结束", + "predicate-all": "所有", + "predicate-overdue": "过期", + "predicate-week": "周", + "predicate-month": "月", + "predicate-quarter": "季度", + "predicate-year": "年", + "predicate-due": "至", + "predicate-modified": "已编辑", + "predicate-created": "已创建", + "predicate-attachment": "附件", + "predicate-description": "描述", + "predicate-checklist": "清单", + "predicate-start": "开始", + "predicate-end": "结束", + "predicate-assignee": "指定人", + "predicate-member": "成员", + "predicate-public": "公开", + "predicate-private": "私有", + "operator-unknown-error": "%s不是维护人员", + "operator-number-expected": "运算符__operator__ 需要相应数字才能获得 '__value__'值", + "operator-sort-invalid": "排序%s无效", + "operator-status-invalid": "%s不是一个有效的状态 ", + "operator-has-invalid": "%s不是有效的检查项", + "operator-limit-invalid": "%s不是有效的限值,限值必须为正整数。", + "next-page": "下一页", + "previous-page": "上一页", + "heading-notes": "注释", + "globalSearch-instructions-heading": "查询指示信息", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "可利用的维护人员", + "globalSearch-instructions-operator-board": "看板:看板中的卡片<title>必须匹配相应的*<title>*", + "globalSearch-instructions-operator-list": "列表:列表内的卡片<title>必须匹配对应的*<title>*", + "globalSearch-instructions-operator-swimlane": "泳道:泳道中的卡片<title>必须匹配相应的*<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - 卡片评论包含 *<text>*.", + "globalSearch-instructions-operator-label": "标签:【标签<color>】卡片<name>要匹配*<color>*或*<name>*", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "可用卡片", + "globalSearch-instructions-status-all": "所有可用和不可用卡片", + "globalSearch-instructions-status-ended": "卡片需要设置结束日期", + "globalSearch-instructions-status-public": "公共卡片只能存在于公共看板", + "globalSearch-instructions-status-private": "私有卡片只能存在于私有看板", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "__operator_limit__:<n> - <n>为每页所显示的卡片数量,必须为正整数。", + "globalSearch-instructions-notes-1": "多操作者需要预先指定。", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "现阶段,天数可以设置为正负整数或者使用 `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` ", + "globalSearch-instructions-notes-4": "文本搜索大小写不敏感。", + "globalSearch-instructions-notes-5": "默认情况下,不搜索存档卡片。", + "link-to-search": "关联至本次查询", + "excel-font": "Arial", + "number": "数字", + "label-colors": "标签颜色", + "label-names": "标签名", + "archived-at": "归档于", + "sort-cards": "排序卡片", + "cardsSortPopup-title": "排序卡片", + "due-date": "截至日期", + "server-error": "服务错误", + "server-error-troubleshooting": "请提交服务器生成的出错信息。\n使用 snap 安装,运行: `sudo snap logs wekan.wekan`\n使用 Docker 安装,运营:‘sudo docker logs wekan-app‘\n", + "title-alphabetically": "标题(按字母顺序)", + "created-at-newest-first": "创建时间(最新)", + "created-at-oldest-first": "创建时间(最旧)", + "links-heading": "链接", + "hide-system-messages-of-all-users": "对所有用户隐藏系统消息", + "now-system-messages-of-all-users-are-hidden": "系统消息已对所有用户隐藏。", + "move-swimlane": "移动泳道", + "moveSwimlanePopup-title": "移动泳道", + "custom-field-stringtemplate": "字符串模板", + "custom-field-stringtemplate-format": "格式(使用 %{value} 作为占位符)", + "custom-field-stringtemplate-separator": "分隔符(使用 或者   表示空格)", + "custom-field-stringtemplate-item-placeholder": "按 enter 键添加更多项目", + "creator": "创建者", + "filesReportTitle": "文件报告", + "orphanedFilesReportTitle": "孤立文件报告", + "reports": "报告", + "rulesReportTitle": "规则报告", + "copy-swimlane": "复制泳道", + "copySwimlanePopup-title": "复制泳道", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/zh-HK.i18n.json b/i18n/zh-HK.i18n.json index 5131bfbd4..b8c19191f 100644 --- a/i18n/zh-HK.i18n.json +++ b/i18n/zh-HK.i18n.json @@ -75,9 +75,16 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "activity-editComment": "edited comment %s", "activity-deleteComment": "deleted comment %s", + "activity-receivedDate": "edited received date to %s of %s", + "activity-startDate": "edited start date to %s of %s", + "activity-dueDate": "edited due date to %s of %s", + "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", "add-board": "Add Board", + "add-template": "Add Template", "add-card": "Add Card", + "add-card-to-top-of-list": "Add Card to Top of List", + "add-card-to-bottom-of-list": "Add Card to Bottom of List", "add-swimlane": "Add Swimlane", "add-subtask": "Add Subtask", "add-checklist": "Add Checklist", @@ -113,6 +120,8 @@ "archives": "Archive", "template": "Template", "templates": "Templates", + "template-container": "Template Container", + "add-template-container": "Add Template Container", "assign-member": "Assign member", "attached": "attached", "attachment": "Attachment", @@ -120,13 +129,14 @@ "attachmentDeletePopup-title": "Delete Attachment?", "attachments": "Attachments", "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (70KB max)", + "avatar-too-big": "The avatar is too large (520KB max)", "back": "Back", "board-change-color": "Change color", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", "board-public-info": "This board will be <strong>public</strong>.", + "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", "boardChangeColorPopup-title": "Change Board Background", "boardChangeTitlePopup-title": "Rename Board", "boardChangeVisibilityPopup-title": "Change Visibility", @@ -138,6 +148,7 @@ "board-view-cal": "Calendar", "board-view-swimlanes": "Swimlanes", "board-view-collapse": "Collapse", + "board-view-gantt": "Gantt", "board-view-lists": "Lists", "bucket-example": "Like “Bucket List” for example", "cancel": "Cancel", @@ -161,6 +172,39 @@ "cardAttachmentsPopup-title": "Attach From", "cardCustomField-datePopup-title": "Change date", "cardCustomFieldsPopup-title": "Edit custom fields", + "cardStartVotingPopup-title": "Start a vote", + "positiveVoteMembersPopup-title": "Proponents", + "negativeVoteMembersPopup-title": "Opponents", + "card-edit-voting": "Edit voting", + "editVoteEndDatePopup-title": "Change vote end date", + "allowNonBoardMembers": "Allow all logged in users", + "vote-question": "Voting question", + "vote-public": "Show who voted what", + "vote-for-it": "for it", + "vote-against": "against", + "deleteVotePopup-title": "Delete vote?", + "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -169,6 +213,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", @@ -191,6 +236,7 @@ "close": "Close", "close-board": "Close Board", "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", + "close-card": "Close Card", "color-black": "black", "color-blue": "blue", "color-crimson": "crimson", @@ -244,6 +290,8 @@ "current": "current", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", + "custom-field-currency": "Currency", + "custom-field-currency-option": "Currency Code", "custom-field-date": "Date", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", @@ -297,13 +345,27 @@ "error-board-notAMember": "You need to be a member of this board to do that", "error-json-malformed": "Your text is not valid JSON", "error-json-schema": "Your JSON data does not include the proper information in the correct format", + "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format", "error-list-doesNotExist": "This list does not exist", "error-user-doesNotExist": "This user does not exist", "error-user-notAllowSelf": "You can not invite yourself", "error-user-notCreated": "This user is not created", "error-username-taken": "This username is already taken", + "error-orgname-taken": "This organization name is already taken", + "error-teamname-taken": "This team name is already taken", "error-email-taken": "Email has already been taken", "export-board": "Export board", + "export-board-json": "Export board to JSON", + "export-board-csv": "Export board to CSV", + "export-board-tsv": "Export board to TSV", + "export-board-excel": "Export board to Excel", + "user-can-not-export-excel": "User can not export Excel", + "export-board-html": "Export board to HTML", + "export-card": "Export card", + "export-card-pdf": "Export card to PDF", + "user-can-not-export-card-to-pdf": "User can not export card to PDF", + "exportBoardPopup-title": "Export board", + "exportCardPopup-title": "Export card", "sort": "Sort", "sort-desc": "Click to Sort List", "list-sort-by": "Sort the List By:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "Filter", "filter-cards": "Filter Cards or Lists", + "filter-dates-label": "Filter by date", + "filter-no-due-date": "No due date", + "filter-overdue": "Overdue", + "filter-due-today": "Due today", + "filter-due-this-week": "Due this week", + "filter-due-tomorrow": "Due tomorrow", "list-filter-label": "Filter List by Title", "filter-clear": "Clear filter", + "filter-labels-label": "Filter by label", "filter-no-label": "No label", + "filter-member-label": "Filter by member", "filter-no-member": "No member", + "filter-assignee-label": "Filter by assignee", + "filter-no-assignee": "No assignee", + "filter-custom-fields-label": "Filter by Custom Fields", "filter-no-custom-fields": "No Custom Fields", "filter-show-archive": "Show archived lists", "filter-hide-empty": "Hide empty lists", "filter-on": "Filter is on", "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", "filter-to-selection": "Filter to selection", + "other-filters-label": "Other Filters", "advanced-filter-label": "Advanced Filter", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Full Name", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "Create Board", "home": "Home", "import": "Import", + "impersonate-user": "Impersonate user", "link": "Link", "import-board": "import board", "import-board-c": "Import board", "import-board-title-trello": "Import board from Trello", "import-board-title-wekan": "Import board from previous export", - "import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.", - "import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.", + "import-board-title-csv": "Import board from CSV/TSV", "from-trello": "From Trello", "from-wekan": "From previous export", + "from-csv": "From CSV/TSV", "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", + "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Paste your valid JSON data here", + "import-csv-placeholder": "Paste your valid CSV/TSV data here", "import-map-members": "Map members", "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", + "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Review members mapping", "import-user-select": "Pick your existing user you want to use as this member", "importMapMembersAddPopup-title": "Select member", @@ -375,9 +453,13 @@ "list-select-cards": "Select all cards in this list", "set-color-list": "Set Color", "listActionPopup-title": "List Actions", + "settingsUserPopup-title": "User Settings", + "settingsTeamPopup-title": "Team Settings", + "settingsOrgPopup-title": "Organization Settings", "swimlaneActionPopup-title": "Swimlane Actions", "swimlaneAddPopup-title": "Add a Swimlane below", "listImportCardPopup-title": "Import a Trello card", + "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", "listMorePopup-title": "More", "link-list": "Link to this list", "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", @@ -396,6 +478,8 @@ "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", "multi-selection": "Multi-Selection", + "multi-selection-label": "Set label for selection", + "multi-selection-member": "Set member for selection", "multi-selection-on": "Multi-Selection is on", "muted": "Muted", "muted-info": "You will never be notified of any changes in this board", @@ -441,8 +525,9 @@ "search": "Search", "rules": "Rules", "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Text to search for?", + "search-example": "Write text you search and press Enter", "select-color": "Select Color", + "select-board": "Select Board", "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", "setWipLimitPopup-title": "Set WIP Limit", "shortcut-assign-self": "Assign yourself to current card", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "Filter my cards", "shortcut-show-shortcuts": "Bring up this shortcuts list", "shortcut-toggle-filterbar": "Toggle Filter Sidebar", + "shortcut-toggle-searchbar": "Toggle Search Sidebar", "shortcut-toggle-sidebar": "Toggle Board Sidebar", "show-cards-minimum-count": "Show cards count if list contains more than", "sidebar-open": "Open Sidebar", @@ -481,7 +567,15 @@ "upload": "Upload", "upload-avatar": "Upload an avatar", "uploaded-avatar": "Uploaded an avatar", + "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", + "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", + "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", + "custom-login-logo-image-url": "Custom Login Logo Image URL", + "custom-login-logo-link-url": "Custom Login Logo Link URL", + "text-below-custom-login-logo": "Text below Custom Login Logo", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", + "import-usernames": "Import Usernames", "view-it": "View it", "warn-list-archived": "warning: this card is in an list at Archive", "watch": "Watch", @@ -553,7 +647,8 @@ "minutes": "minutes", "seconds": "seconds", "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Auto create field to all cards", + "automatically-field-on-card": "Add field to new cards", + "always-field-on-card": "Add field to all cards", "showLabel-field-on-card": "Show field label on minicard", "yes": "Yes", "no": "No", @@ -561,6 +656,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "createdAt": "Created at", + "modifiedAt": "Modified at", "verified": "Verified", "active": "Active", "card-received": "Received", @@ -575,6 +671,7 @@ "setListColorPopup-title": "Choose a color", "assigned-by": "Assigned By", "requested-by": "Requested By", + "card-sorting-by-number": "Card sorting by number", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -614,13 +711,16 @@ "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", "r-no-rules": "No rules", + "r-trigger": "Trigger", + "r-action": "Action", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", - "r-added-to": "added to", + "r-added-to": "Added to", "r-removed-from": "Removed from", "r-the-board": "the board", "r-list": "list", + "list": "List", "set-filter": "Set Filter", "r-moved-to": "Moved to", "r-moved-from": "Moved from", @@ -665,6 +765,7 @@ "r-of-checklist": "of checklist", "r-send-email": "Send an email", "r-to": "to", + "r-of": "of", "r-subject": "subject", "r-rule-details": "Rule details", "r-d-move-to-top-gen": "Move card to top of its list", @@ -725,6 +826,8 @@ "display-authentication-method": "Display Authentication Method", "default-authentication-method": "Default Authentication Method", "duplicate-board": "Duplicate Board", + "org-number": "The number of organizations is:", + "team-number": "The number of teams is:", "people-number": "The number of people is:", "swimlaneDeletePopup-title": "Delete Swimlane ?", "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", @@ -750,6 +853,8 @@ "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", + "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", + "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", "show-desktop-drag-handles": "Show desktop drag handles", @@ -758,12 +863,200 @@ "addmore-detail": "Add a more detailed description", "show-on-card": "Show on Card", "new": "New", + "editOrgPopup-title": "Edit Organization", + "newOrgPopup-title": "New Organization", + "editTeamPopup-title": "Edit Team", + "newTeamPopup-title": "New Team", "editUserPopup-title": "Edit User", "newUserPopup-title": "New User", "notifications": "Notifications", "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", + "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename" + "allowRenamePopup-title": "Allow Rename", + "start-day-of-week": "Set day of the week start", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "status": "Status", + "swimlane": "Swimlane", + "owner": "Owner", + "last-modified-at": "Last modified at", + "last-activity": "Last activity", + "voting": "Voting", + "archived": "Archived", + "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", + "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", + "hide-checked-items": "Hide checked items", + "task": "Task", + "create-task": "Create Task", + "ok": "OK", + "organizations": "Organizations", + "teams": "Teams", + "displayName": "Display Name", + "shortName": "Short Name", + "website": "Website", + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "board": "Board", + "context-separator": "/", + "myCardsSortChange-title": "My Cards Sort", + "myCardsSortChangePopup-title": "My Cards Sort", + "myCardsSortChange-choice-board": "By Board", + "myCardsSortChange-choice-dueat": "By Due Date", + "dueCards-title": "Due Cards", + "dueCardsViewChange-title": "Due Cards View", + "dueCardsViewChangePopup-title": "Due Cards View", + "dueCardsViewChange-choice-me": "Me", + "dueCardsViewChange-choice-all": "All Users", + "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", + "broken-cards": "Broken Cards", + "board-title-not-found": "Board '%s' not found.", + "swimlane-title-not-found": "Swimlane '%s' not found.", + "list-title-not-found": "List '%s' not found.", + "label-not-found": "Label '%s' not found.", + "label-color-not-found": "Label color %s not found.", + "user-username-not-found": "Username '%s' not found.", + "comment-not-found": "Card with comment containing text '%s' not found.", + "globalSearch-title": "Search All Boards", + "no-cards-found": "No Cards Found", + "one-card-found": "One Card Found", + "n-cards-found": "%s Cards Found", + "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", + "operator-board": "board", + "operator-board-abbrev": "b", + "operator-swimlane": "swimlane", + "operator-swimlane-abbrev": "s", + "operator-list": "list", + "operator-list-abbrev": "l", + "operator-label": "label", + "operator-label-abbrev": "#", + "operator-user": "user", + "operator-user-abbrev": "@", + "operator-member": "member", + "operator-member-abbrev": "m", + "operator-assignee": "assignee", + "operator-assignee-abbrev": "a", + "operator-creator": "creator", + "operator-status": "status", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", + "operator-sort": "sort", + "operator-comment": "comment", + "operator-has": "has", + "operator-limit": "limit", + "predicate-archived": "archived", + "predicate-open": "open", + "predicate-ended": "ended", + "predicate-all": "all", + "predicate-overdue": "overdue", + "predicate-week": "week", + "predicate-month": "month", + "predicate-quarter": "quarter", + "predicate-year": "year", + "predicate-due": "due", + "predicate-modified": "modified", + "predicate-created": "created", + "predicate-attachment": "attachment", + "predicate-description": "description", + "predicate-checklist": "checklist", + "predicate-start": "start", + "predicate-end": "end", + "predicate-assignee": "assignee", + "predicate-member": "member", + "predicate-public": "public", + "predicate-private": "private", + "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", + "operator-sort-invalid": "sort of '%s' is invalid", + "operator-status-invalid": "'%s' is not a valid status", + "operator-has-invalid": "%s is not a valid existence check", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "Next Page", + "previous-page": "Previous Page", + "heading-notes": "Notes", + "globalSearch-instructions-heading": "Search Instructions", + "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", + "globalSearch-instructions-operators": "Available operators:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "Multiple operators may be specified.", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "Text searches are case insensitive.", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "Link to this search", + "excel-font": "Arial", + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names", + "archived-at": "archived at", + "sort-cards": "Sort Cards", + "cardsSortPopup-title": "Sort Cards", + "due-date": "Due Date", + "server-error": "Server Error", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "Title (Alphabetically)", + "created-at-newest-first": "Created At (Newest First)", + "created-at-oldest-first": "Created At (Oldest First)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "Move Swimlane", + "moveSwimlanePopup-title": "Move Swimlane", + "custom-field-stringtemplate": "String Template", + "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "Creator", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "Reports", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "Copy Swimlane", + "copySwimlanePopup-title": "Copy Swimlane", + "display-card-creator": "Display Card Creator", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/i18n/zh-TW.i18n.json b/i18n/zh-TW.i18n.json index 1b7d3f465..77c26eeda 100644 --- a/i18n/zh-TW.i18n.json +++ b/i18n/zh-TW.i18n.json @@ -64,8 +64,8 @@ "activity-unchecked-item": "未勾選 %s 於清單 %s 共 %s", "activity-checklist-added": "已新增待辦清單 %s", "activity-checklist-removed": "已刪除%s的待辦清單", - "activity-checklist-completed": "已完成清單 %s 共 %s", - "activity-checklist-uncompleted": "未完成清單 %s 共 %s", + "activity-checklist-completed": "已完成待辦清單 %s 共 %s 項", + "activity-checklist-uncompleted": "未完成待辦清單 %s 共 %s 項", "activity-checklist-item-added": "新增待辦清單項目從 %s 到 %s", "activity-checklist-item-removed": "已從 '%s' 於 %s中 移除一個清單項", "add": "新增", @@ -75,10 +75,17 @@ "activity-checklist-uncompleted-card": "未完成清單 %s", "activity-editComment": "評論已編輯", "activity-deleteComment": "評論已刪除", + "activity-receivedDate": "已編輯收到日期為 %s %s", + "activity-startDate": "已編輯起始日期為 %s %s", + "activity-dueDate": "已編輯截止日期為 %s %s", + "activity-endDate": "已編輯結束日期為 %s %s ", "add-attachment": "新增附件", "add-board": "新增看板", + "add-template": "新增範本", "add-card": "新增卡片", - "add-swimlane": "新增泳道圖", + "add-card-to-top-of-list": "新增卡片至清單頂部", + "add-card-to-bottom-of-list": "新增卡片至清單底部", + "add-swimlane": "新增泳道", "add-subtask": "新增子任務", "add-checklist": "新增待辦清單", "add-checklist-item": "新增項目", @@ -91,19 +98,19 @@ "admin": "管理員", "admin-desc": "可以瀏覽並編輯卡片,移除成員,並且更改該看板的設定", "admin-announcement": "通知", - "admin-announcement-active": "激活系統通知", - "admin-announcement-title": "管理員的通知", + "admin-announcement-active": "啟用系統公告", + "admin-announcement-title": "來自管理員的公告訊息", "all-boards": "全部看板", "and-n-other-card": "和其他 __count__ 個卡片", "and-n-other-card_plural": "和其他 __count__ 個卡片", "apply": "應用", - "app-is-offline": "加載中,請稍後。刷新頁面將導致數據丟失,如果加載長時間不起作用,請檢查服務器是否已經停止工作。", + "app-is-offline": "頁面載入中,請稍後。重新整理頁面將會造成尚未儲存的資料遺失,如果載入太久,請檢查伺服器是否已經停止工作。", "archive": "封存", "archive-all": "全部封存", "archive-board": "將看板封存", "archive-card": "將卡片封存", "archive-list": "將清單封存", - "archive-swimlane": "將泳道封存", + "archive-swimlane": "將泳道流程圖封存", "archive-selection": "將選擇封存", "archiveBoardPopup-title": "是否封存看板?", "archived-items": "封存", @@ -111,8 +118,10 @@ "restore-board": "還原看板", "no-archived-boards": "沒有封存的看板。", "archives": "封存", - "template": "模板", - "templates": "模板", + "template": "範本", + "templates": "範本", + "template-container": "範本容器", + "add-template-container": "新增範本容器", "assign-member": "分配成員", "attached": "附加", "attachment": "附件", @@ -120,31 +129,33 @@ "attachmentDeletePopup-title": "刪除附件?", "attachments": "附件", "auto-watch": "自動訂閱新建立的看板", - "avatar-too-big": "頭像過大 (上限 70 KB)", + "avatar-too-big": "頭貼過大(最大520kb)", "back": "返回", "board-change-color": "更改顏色", - "board-nb-stars": "%s 星標", + "board-nb-stars": "%s 星星", "board-not-found": "看板不存在", "board-private-info": "該看板將被設為 <strong>私有</strong>.", "board-public-info": "該看板將被設為 <strong>公開</strong>.", + "board-drag-drop-reorder-or-click-open": "拖放以重新排序看板圖示。點擊看板圖示開啟看板。", "boardChangeColorPopup-title": "修改看板背景", "boardChangeTitlePopup-title": "重新命名看板", "boardChangeVisibilityPopup-title": "更改可見度", "boardChangeWatchPopup-title": "更改訂閱狀態", "boardMenuPopup-title": "看板設定", - "boardChangeViewPopup-title": "看板視圖", + "boardChangeViewPopup-title": "看板檢視", "boards": "看板", - "board-view": "看板視圖", - "board-view-cal": "日歷", - "board-view-swimlanes": "泳道圖", + "board-view": "看板檢視", + "board-view-cal": "日曆", + "board-view-swimlanes": "泳道", "board-view-collapse": "損毀", + "board-view-gantt": "甘特圖", "board-view-lists": "清單", "bucket-example": "例如 “目標清單”", "cancel": "取消", "card-archived": "封存這個卡片。", "board-archived": "封存這個看板。", "card-comments-title": "該卡片有 %s 條評論", - "card-delete-notice": "徹底刪除的操作不可恢覆,你將會丟失該卡片相關的所有操作記錄。", + "card-delete-notice": "永久刪除是無法復原的,你將會失去這張卡片的所有相關操作記錄。", "card-delete-pop": "所有的活動將從活動摘要中被移除且您將無法重新打開該卡片。此操作無法撤銷。", "card-delete-suggest-archive": "您可以移動卡片到活動以便從看板中刪除並保持活動。", "card-due": "到期日", @@ -157,18 +168,52 @@ "card-labels-title": "更改該卡片上的標籤", "card-members-title": "在該卡片中新增或移除看板成員", "card-start": "開始", - "card-start-on": "始於", + "card-start-on": "開始於", "cardAttachmentsPopup-title": "附件來源", "cardCustomField-datePopup-title": "修改日期", "cardCustomFieldsPopup-title": "編輯自訂欄位", + "cardStartVotingPopup-title": "建立投票", + "positiveVoteMembersPopup-title": "支持", + "negativeVoteMembersPopup-title": "反對", + "card-edit-voting": "編輯投票", + "editVoteEndDatePopup-title": "更改投票截止日期", + "allowNonBoardMembers": "允許所有已登入用戶", + "vote-question": "投票題目", + "vote-public": "記名投票", + "vote-for-it": "同意", + "vote-against": "反對", + "deleteVotePopup-title": "刪除投票?", + "vote-delete-pop": "永久刪除是無法復原的,你將會失去這張卡片的所有相關操作記錄。", + "cardStartPlanningPokerPopup-title": "開始規劃撲克", + "card-edit-planning-poker": "編輯規劃撲克", + "editPokerEndDatePopup-title": "改變規劃撲克投票截止日", + "poker-question": "規劃撲克", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "完成", + "poker-result-votes": "投票", + "poker-result-who": "誰", + "poker-replay": "重播", + "set-estimation": "設定預估時間", + "deletePokerPopup-title": "刪除規劃撲克?", + "poker-delete-pop": "刪除是永遠的,你會失去所有與此規劃撲克相關動作關聯", "cardDeletePopup-title": "徹底刪除卡片?", "cardDetailsActionsPopup-title": "卡片操作", "cardLabelsPopup-title": "標籤", "cardMembersPopup-title": "成員", "cardMorePopup-title": "更多", - "cardTemplatePopup-title": "新建模板", + "cardTemplatePopup-title": "建立範本", "cards": "卡片", "cards-count": "卡片", + "cards-count-one": "卡片", "casSignIn": "以 CAS 登入", "cardType-card": "卡片", "cardType-linkedCard": "已連結卡片", @@ -176,12 +221,12 @@ "change": "變更", "change-avatar": "更換大頭貼", "change-password": "變更密碼", - "change-permissions": "更改許可權", + "change-permissions": "變更權限", "change-settings": "更改設定", "changeAvatarPopup-title": "更換大頭貼", "changeLanguagePopup-title": "更改語系", "changePasswordPopup-title": "變更密碼", - "changePermissionsPopup-title": "更改許可權", + "changePermissionsPopup-title": "變更權限", "changeSettingsPopup-title": "更改設定", "subtasks": "子任務", "checklists": "待辦清單", @@ -190,7 +235,8 @@ "clipboard": "剪貼簿貼上或者拖曳檔案", "close": "關閉", "close-board": "關閉看板", - "close-board-pop": "您可以通過點擊主頁面中的「封存」按鈕來恢復看板。", + "close-board-pop": "您可以透過點擊首頁中的「封存」按鈕來還原看板。", + "close-card": "關閉卡片", "color-black": "黑色", "color-blue": "藍色", "color-crimson": "深紅", @@ -218,7 +264,7 @@ "color-yellow": "黃色", "unset-color": "未設定", "comment": "評論", - "comment-placeholder": "撰寫文字 (可使用 Markdown 語法)", + "comment-placeholder": "撰寫文字", "comment-only": "僅能評論", "comment-only-desc": "只能在卡片上發表評論。", "no-comments": "暫無評論", @@ -232,7 +278,7 @@ "linkCardPopup-title": "連結卡片", "searchElementPopup-title": "搜尋", "copyCardPopup-title": "複製卡片", - "copyChecklistToManyCardsPopup-title": "複製待辦清單的樣板到多個卡片", + "copyChecklistToManyCardsPopup-title": "複製待辦清單的範本到多個卡片", "copyChecklistToManyCardsPopup-instructions": "使用此 JSON 格式來表示目標卡片的標題和描述", "copyChecklistToManyCardsPopup-format": "[ {\\\"title\\\": \\\"第一個卡片標題\\\", \\\"description\\\":\\\"第一個卡片描述\\\"}, {\\\"title\\\":\\\"第二個卡片標題\\\",\\\"description\\\":\\\"第二個卡片描述\\\"},{\\\"title\\\":\\\"最後一個卡片標題\\\",\\\"description\\\":\\\"最後一個卡片描述\\\"} ]", "create": "建立", @@ -244,6 +290,8 @@ "current": "目前", "custom-field-delete-pop": "此操作將會從所有卡片中移除自訂欄位以及銷毀歷史紀錄,並且無法撤消。", "custom-field-checkbox": "複選框", + "custom-field-currency": "貨幣", + "custom-field-currency-option": "貨幣代碼", "custom-field-date": "日期", "custom-field-dropdown": "下拉式選單", "custom-field-dropdown-none": "(無)", @@ -297,13 +345,27 @@ "error-board-notAMember": "需要成為看板成員才能執行此操作", "error-json-malformed": "不是有效的 JSON", "error-json-schema": "JSON 資料沒有用正確的格式包含合適的資訊", + "error-csv-schema": "CSV(Comma Separated Values)或TSV (Tab Separated Values)資料沒有用正確的格式包含合適的資訊", "error-list-doesNotExist": "不存在此列表", "error-user-doesNotExist": "該使用者不存在", "error-user-notAllowSelf": "不允許對自己執行此操作", "error-user-notCreated": "該使用者未能成功新增", "error-username-taken": "這個使用者名稱已被使用", - "error-email-taken": "電子信箱已被使用", + "error-orgname-taken": "這個組織名稱已被使用\n ", + "error-teamname-taken": "這個團隊名稱已被使用\n ", + "error-email-taken": "Email 已被使用", "export-board": "匯出看板", + "export-board-json": "匯出看板為JSON格式", + "export-board-csv": "匯出看板為CSV格式", + "export-board-tsv": "匯出看板為TSV格式\n ", + "export-board-excel": "匯出看板至 Excel", + "user-can-not-export-excel": "使用者無法匯出至 Excel", + "export-board-html": "匯出看板為HTML格式\n ", + "export-card": "匯出卡片", + "export-card-pdf": "匯出卡片至PDF", + "user-can-not-export-card-to-pdf": "使用者不能將卡片匯出至PDF", + "exportBoardPopup-title": "匯出看板", + "exportCardPopup-title": "匯出卡片", "sort": "排序", "sort-desc": "點選排序清單", "list-sort-by": "清單排序依照:", @@ -315,16 +377,28 @@ "list-label-short-sort": "(M)", "filter": "篩選", "filter-cards": "篩選卡片或清單", + "filter-dates-label": "篩選: 日期", + "filter-no-due-date": "沒有到期日", + "filter-overdue": "逾期", + "filter-due-today": "今天到期", + "filter-due-this-week": "本週到期", + "filter-due-tomorrow": "明天到期", "list-filter-label": "篩選清單依據標題", "filter-clear": "清除篩選條件", + "filter-labels-label": "篩選: 標籤", "filter-no-label": "沒有標籤", + "filter-member-label": "篩選: 成員", "filter-no-member": "沒有成員", + "filter-assignee-label": "篩選: 代理人", + "filter-no-assignee": "沒有代理人", + "filter-custom-fields-label": "篩選: 自訂欄位", "filter-no-custom-fields": "沒有自訂欄位", "filter-show-archive": "顯示封存的清單", "filter-hide-empty": "隱藏空清單", "filter-on": "篩選器已開啟", "filter-on-desc": "你正在篩選該看板上的卡片,點此編輯篩選條件。", "filter-to-selection": "選擇的篩選條件", + "other-filters-label": "其他過濾器", "advanced-filter-label": "進階篩選", "advanced-filter-description": "進階篩選可以使用包含如下運算子的字串進行過濾:== != <= >= && || ( ) 。運算子之間用空格隔開。輸入文字和數值就可以過濾所有自訂內容。例如:Field1 == Value1。注意:如果內容或數值包含空格,需要用單引號。例如: 'Field 1' == 'Value 1'。要跳過單個控制字元(' \\/),請使用 \\ 跳脫字元。例如: Field1 = I\\'m。支援組合使用多個條件,例如: F1 == V1 || F1 == V2。通常以從左到右的順序進行判斷。可以透過括號修改順序,例如:F1 == V1 && ( F2 == V2 || F2 == V3 )。也支援使用 正規表示式 (Regex) 搜尋內容。", "fullname": "全稱", @@ -333,21 +407,25 @@ "headerBarCreateBoardPopup-title": "建立看板", "home": "首頁", "import": "匯入", + "impersonate-user": "模擬使用者", "link": "連結", "import-board": "匯入看板", "import-board-c": "匯入看板", "import-board-title-trello": "匯入在 Trello 的看板", "import-board-title-wekan": "從上次的匯出檔匯入看板", - "import-sandstorm-backup-warning": "在檢查此顆粒是否關閉和再次打開之前,不要刪除從原始匯出的看板或 Trello 匯入的數據,否則看板會發生未知的錯誤,這意味著資料已遺失。", - "import-sandstorm-warning": "匯入資料將會移除所有現有的看版資料,並取代成此次匯入的看板資料", + "import-board-title-csv": "從 CSV/TSV 匯入看板", "from-trello": "來自 Trello", "from-wekan": "從上次的匯出檔", + "from-csv": "來自 CSV/TSV", "import-board-instruction-trello": "在你的Trello看板中,點選“功能表”,然後選擇“更多”,“列印與匯出”,“匯出為 JSON” 並拷貝結果文本", + "import-board-instruction-csv": "貼上你的 CSV(Comma Separated Values)/TSV(Tab Separated Values) 資料", "import-board-instruction-wekan": "在您的看板,點擊“選單”,然後“匯出看板”,複製下載文件中的文本。", "import-board-instruction-about-errors": "如果在匯入看板時出現錯誤,匯入工作可能仍然在進行中,並且看板已經出現在全部看板頁。", "import-json-placeholder": "貼上您有效的 JSON 資料至此", + "import-csv-placeholder": "貼上您有效的 CSV/TSV 資料至此", "import-map-members": "複製成員", "import-members-map": "您匯入的看板有一些成員,請複製這些成員到您匯入的用戶。", + "import-members-map-note": "備註: 未對應的成員將分配給目前使用者。", "import-show-user-mapping": "核對複製的成員", "import-user-select": "選擇現有使用者作為成員", "importMapMembersAddPopup-title": "選擇成員", @@ -375,15 +453,19 @@ "list-select-cards": "選擇清單中的所有卡片", "set-color-list": "設定顏色", "listActionPopup-title": "清單操作", + "settingsUserPopup-title": "使用者設定", + "settingsTeamPopup-title": "團隊設定", + "settingsOrgPopup-title": "組織設定", "swimlaneActionPopup-title": "泳道動作", "swimlaneAddPopup-title": "在下面新增泳道", "listImportCardPopup-title": "匯入 Trello 卡片", + "listImportCardsTsvPopup-title": "匯入 Excel CSV/TSV", "listMorePopup-title": "更多", "link-list": "連結到這個清單", "list-delete-pop": "所有的動作都將從活動動態中被移除且您將無法再開啟該清單\b。此操作無法復原。", "list-delete-suggest-archive": "您可以移動清單到封存以將其從看板中移除並保留活動。", "lists": "清單", - "swimlanes": "泳道圖", + "swimlanes": "泳道", "log-out": "登出", "log-in": "登入", "loginPopup-title": "登入", @@ -396,6 +478,8 @@ "moveCardToTop-title": "移至最上面", "moveSelectionPopup-title": "移動選取的項目", "multi-selection": "多選", + "multi-selection-label": "設定標籤到選擇項目", + "multi-selection-member": "設定成員到選擇項目", "multi-selection-on": "多選啟用", "muted": "取消任何通知", "muted-info": "您將不會收到有關這個看板的任何訊息", @@ -441,8 +525,9 @@ "search": "搜尋", "rules": "規則", "search-cards": "搜尋看板內的卡片/清單標題、描述、自訂欄位", - "search-example": "搜尋", + "search-example": "輸入欲查尋的文件並按下 Enter 鍵", "select-color": "選擇顏色", + "select-board": "選擇看板", "set-wip-limit-value": "設定此清單中的最大任務數", "setWipLimitPopup-title": "設定 WIP 限制", "shortcut-assign-self": "分配當前卡片給自己", @@ -453,6 +538,7 @@ "shortcut-filter-my-cards": "過濾我的卡片", "shortcut-show-shortcuts": "顯示此快速鍵清單", "shortcut-toggle-filterbar": "切換過濾程式邊欄", + "shortcut-toggle-searchbar": "切換搜索欄", "shortcut-toggle-sidebar": "切換面板邊欄", "show-cards-minimum-count": "顯示卡片數量,當內容超過數量", "sidebar-open": "開啟側邊欄", @@ -481,7 +567,15 @@ "upload": "上傳", "upload-avatar": "上傳大頭貼", "uploaded-avatar": "大頭貼已經上傳", + "custom-top-left-corner-logo-image-url": "自訂左上商標圖示網址", + "custom-top-left-corner-logo-link-url": "自訂左上商標連結網址", + "custom-top-left-corner-logo-height": "自訂左上商標圖示高度。預設值:27", + "custom-login-logo-image-url": "自訂登入商標圖示網址", + "custom-login-logo-link-url": "自訂登入商標連結網址", + "text-below-custom-login-logo": "登入商標下方文字", + "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "使用者名稱", + "import-usernames": "匯入使用者名稱", "view-it": "檢視", "warn-list-archived": "警告: 卡片位在封存的清單中", "watch": "追蹤", @@ -491,14 +585,14 @@ "welcome-swimlane": "里程碑 1", "welcome-list1": "基本", "welcome-list2": "進階", - "card-templates-swimlane": "卡片模板", - "list-templates-swimlane": "清單模板", - "board-templates-swimlane": "看板模板", + "card-templates-swimlane": "卡片範本", + "list-templates-swimlane": "清單範本", + "board-templates-swimlane": "看板範本", "what-to-do": "要做什麼?", "wipLimitErrorPopup-title": "無效的最大任務數", "wipLimitErrorPopup-dialog-pt1": "此清單中的任務數量已經超過了設定的最大任務數。", "wipLimitErrorPopup-dialog-pt2": "請將一些任務移出此清單,或者設定一個更大的最大任務數。", - "admin-panel": "控制台", + "admin-panel": "系統設定", "settings": "設定", "people": "成員", "registration": "註冊", @@ -537,30 +631,32 @@ "Node_version": "Node.js 版本", "Meteor_version": "Meteor 版本", "MongoDB_version": "MongoDB 版本", - "MongoDB_storage_engine": "MongoDB 存儲引擎", + "MongoDB_storage_engine": "MongoDB 儲存引擎", "MongoDB_Oplog_enabled": "MongoDB Oplog 已啟用", - "OS_Arch": "系統架構", + "OS_Arch": "作業系統架構", "OS_Cpus": "系統 CPU 數量", "OS_Freemem": "系統可用記憶體", "OS_Loadavg": "系統平均負載", - "OS_Platform": "系統平臺", - "OS_Release": "系統發佈版本", + "OS_Platform": "作業系統平台", + "OS_Release": "作業系統版本", "OS_Totalmem": "系統總記憶體", - "OS_Type": "系統類型", + "OS_Type": "作業系統類型", "OS_Uptime": "系統運行時間", "days": "天", "hours": "小時", "minutes": "分鐘", "seconds": "秒", "show-field-on-card": "在卡片上顯示這個欄位", - "automatically-field-on-card": "自動在所有卡片建立欄位", + "automatically-field-on-card": "新增欄位至新卡片", + "always-field-on-card": "新增欄位至所有卡片", "showLabel-field-on-card": "在迷你卡片中顯示欄位標籤", "yes": "是", "no": "否", - "accounts": "賬號", - "accounts-allowEmailChange": "允許變更電子信箱", + "accounts": "帳號", + "accounts-allowEmailChange": "允許變更 Email", "accounts-allowUserNameChange": "允許修改使用者名稱", "createdAt": "新增於", + "modifiedAt": "編輯於", "verified": "已驗證", "active": "啟用", "card-received": "已接收", @@ -575,7 +671,8 @@ "setListColorPopup-title": "選擇顏色", "assigned-by": "分配者", "requested-by": "請求者", - "board-delete-notice": "刪除時永久操作,將會丟失此看板上的所有清單、卡片和動作。", + "card-sorting-by-number": "卡片依編號排序", + "board-delete-notice": "永久刪除是無法復原的,你將會失去這個看板上的所有清單、卡片和動作。", "delete-board-confirm-popup": "所有清單、卡片、標籤和活動都會被刪除,將無法恢覆看板內容。不支援撤銷。", "boardDeletePopup-title": "刪除看板?", "delete-board": "刪除看板", @@ -614,13 +711,16 @@ "r-delete-rule": "刪除規則", "r-new-rule-name": "新規則標題", "r-no-rules": "暫無規則", + "r-trigger": "觸發", + "r-action": "操作", "r-when-a-card": "當一張卡片", "r-is": "是", "r-is-moved": "已經移動", - "r-added-to": "新增到", + "r-added-to": "新增至", "r-removed-from": "已移除", "r-the-board": "該看板", "r-list": "清單", + "list": "清單", "set-filter": "設定過濾器", "r-moved-to": "移至", "r-moved-from": "已移動", @@ -665,6 +765,7 @@ "r-of-checklist": "清單的", "r-send-email": "寄送郵件", "r-to": "收件人", + "r-of": "的", "r-subject": "主旨", "r-rule-details": "詳細規則", "r-d-move-to-top-gen": "將卡片移到所屬清單頂部", @@ -724,7 +825,9 @@ "error-ldap-login": "嘗試登入時出現錯誤", "display-authentication-method": "顯示認證方式", "default-authentication-method": "預設認證方式", - "duplicate-board": "重複的看板", + "duplicate-board": "複製看板", + "org-number": "組織數是:", + "team-number": "團隊數是:", "people-number": "人數是:", "swimlaneDeletePopup-title": "是否刪除泳道?", "swimlane-delete-pop": "所有動作將從活動來源中刪除,您將無法恢復泳道。此操作無法還原。", @@ -743,13 +846,15 @@ "almostdue": "當前到期時間%s即將到來", "pastdue": "當前到期時間%s已過", "duenow": "當前到期時間%s為今天", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", + "act-newDue": "__list__/__card__ 包含第1個到期提醒 [__board__]", + "act-withDue": "__list__/__card__ 到期提醒 [__board__]", "act-almostdue": "__card__ 的當前到期提醒(__timeValue__) 正在接近", "act-pastdue": "__card__ 的當前到期提醒(__timeValue__) 已經過去了", "act-duenow": "__card__ 的當前到期提醒(__timeValue__) 現在到期", - "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", + "act-atUserComment": "你在 [__board__] __list__/__card__ 被提到", "delete-user-confirm-popup": "確定要刪除此帳戶嗎?此操作無法還原。", + "delete-team-confirm-popup": "確定要刪除此團隊? 此動作不能復原", + "delete-org-confirm-popup": "確定刪除此組織? 此動作不能復原", "accounts-allowUserDelete": "允許用戶自行刪除其帳戶", "hide-minicard-label-text": "隱藏迷你卡片標籤內文", "show-desktop-drag-handles": "顯示桌面拖曳工具", @@ -758,12 +863,200 @@ "addmore-detail": "新增更多詳細描述", "show-on-card": "在卡片上顯示", "new": "新增", + "editOrgPopup-title": "編輯組織", + "newOrgPopup-title": "新建組織", + "editTeamPopup-title": "編輯團隊", + "newTeamPopup-title": "新建團隊", "editUserPopup-title": "編輯使用者", "newUserPopup-title": "新增使用者", "notifications": "通知", "view-all": "檢視全部", "filter-by-unread": "篩選: 未讀", "mark-all-as-read": "標示全部已讀", + "remove-all-read": "移除所有已讀", "allow-rename": "允許更名", - "allowRenamePopup-title": "允許更名" + "allowRenamePopup-title": "允許更名", + "start-day-of-week": "一周的第一天", + "monday": "週一", + "tuesday": "週二", + "wednesday": "週三", + "thursday": "週四", + "friday": "週五", + "saturday": "週六", + "sunday": "週日", + "status": "狀態", + "swimlane": "泳道流程圖", + "owner": "擁有者", + "last-modified-at": "最後修改時間", + "last-activity": "最後活動", + "voting": "投票", + "archived": "封存", + "delete-linked-card-before-this-card": "在刪除指向此卡片的鏈結卡之前,您不能刪除此卡片", + "delete-linked-cards-before-this-list": "在刪除指向該清單中卡片的鏈結卡之前,您不能刪除此清單", + "hide-checked-items": "隱藏已勾選項目", + "task": "任務", + "create-task": "建立任務", + "ok": "OK", + "organizations": "組織", + "teams": "團隊", + "displayName": "顯示用名稱", + "shortName": "排序用名稱", + "website": "網站", + "person": "人物", + "my-cards": "我的卡片", + "card": "卡片", + "board": "看板", + "context-separator": "/", + "myCardsSortChange-title": "我的卡片排序", + "myCardsSortChangePopup-title": "我的卡片排序", + "myCardsSortChange-choice-board": "依看板", + "myCardsSortChange-choice-dueat": "依到期日", + "dueCards-title": "到期卡片", + "dueCardsViewChange-title": "到期卡片視圖", + "dueCardsViewChangePopup-title": "到期卡片視圖", + "dueCardsViewChange-choice-me": "我", + "dueCardsViewChange-choice-all": "全部使用者", + "dueCardsViewChange-choice-all-description": "顯示看板內所有已設定到期日,且使用者有權限的未完成卡片", + "broken-cards": "損毀卡片", + "board-title-not-found": "看板%s不存在", + "swimlane-title-not-found": "泳道流程圖%s不存在", + "list-title-not-found": "清單%s不存在", + "label-not-found": "標籤%s不存在", + "label-color-not-found": "找不到標籤顏色%s", + "user-username-not-found": "使用者%s不存在", + "comment-not-found": "找不到評論包含%s的卡片", + "globalSearch-title": "搜尋所有看板", + "no-cards-found": "找不到卡片", + "one-card-found": "找到1張卡片", + "n-cards-found": "找到%s張卡片", + "n-n-of-n-cards-found": "找到卡片於 __total__ 中的 __start__-__end__", + "operator-board": "看板", + "operator-board-abbrev": "b", + "operator-swimlane": "泳道流程圖", + "operator-swimlane-abbrev": "s", + "operator-list": "清單", + "operator-list-abbrev": "l", + "operator-label": "標籤", + "operator-label-abbrev": "#", + "operator-user": "使用者", + "operator-user-abbrev": "@", + "operator-member": "成員", + "operator-member-abbrev": "m", + "operator-assignee": "代理人", + "operator-assignee-abbrev": "a", + "operator-creator": "建立者", + "operator-status": "狀態", + "operator-due": "至", + "operator-created": "已建立", + "operator-modified": "已修改", + "operator-sort": "排序", + "operator-comment": "評論", + "operator-has": "擁有", + "operator-limit": "限制", + "predicate-archived": "已封存", + "predicate-open": "開啟", + "predicate-ended": "已結束", + "predicate-all": "全部", + "predicate-overdue": "逾期", + "predicate-week": "周", + "predicate-month": "月", + "predicate-quarter": "季", + "predicate-year": "年", + "predicate-due": "至", + "predicate-modified": "已修改", + "predicate-created": "已建立", + "predicate-attachment": "附件", + "predicate-description": "描述", + "predicate-checklist": "待辦清單", + "predicate-start": "開始", + "predicate-end": "完成", + "predicate-assignee": "代理人", + "predicate-member": "成員", + "predicate-public": "公開", + "predicate-private": "私有", + "operator-unknown-error": "%s不是運算子", + "operator-number-expected": "運算子__operator__預期是數字,但得到 '__value__'", + "operator-sort-invalid": "某種\"%s\"無效", + "operator-status-invalid": "\"%s\"是無效狀態", + "operator-has-invalid": "%s是無效的存在檢查", + "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", + "next-page": "下一頁", + "previous-page": "上一頁", + "heading-notes": "筆記", + "globalSearch-instructions-heading": "搜尋指引", + "globalSearch-instructions-description": "搜索可以包括運算子以優化搜索結果。通過冒號分隔的運算子名稱和值來指定運算子。例如,運算子指定 `list:Blocked` 則會將搜索範圍限制在名為 *Blocked* 的清單中。如果值包含空格或特殊字符,則必須將其用引號標記(例如,`__operator_list__:\"To Review\"`)。", + "globalSearch-instructions-operators": "可用的執行者:", + "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", + "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", + "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", + "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", + "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", + "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", + "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", + "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", + "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", + "globalSearch-instructions-notes-1": "將會指定給多位執行者", + "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", + "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", + "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", + "globalSearch-instructions-notes-4": "英文搜尋忽略大小寫", + "globalSearch-instructions-notes-5": "By default archived cards are not searched.", + "link-to-search": "連結到此搜尋", + "excel-font": "Arial", + "number": "數字", + "label-colors": "標籤顏色", + "label-names": "標籤名稱", + "archived-at": "封存於", + "sort-cards": "排序卡片", + "cardsSortPopup-title": "排序卡片", + "due-date": "到期日", + "server-error": "伺服器錯誤", + "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", + "title-alphabetically": "標題 (按字母順序)", + "created-at-newest-first": "創建於(最新優先)", + "created-at-oldest-first": "創建於(最早優先)", + "links-heading": "Links", + "hide-system-messages-of-all-users": "Hide system messages of all users", + "now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden", + "move-swimlane": "移動泳道流程圖", + "moveSwimlanePopup-title": "移動泳道流程圖", + "custom-field-stringtemplate": "文字範本", + "custom-field-stringtemplate-format": "格式 (使用 %{value} 作為佔位符)", + "custom-field-stringtemplate-separator": "Separator (use or   for a space)", + "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", + "creator": "創建者", + "filesReportTitle": "Files Report", + "orphanedFilesReportTitle": "Orphaned Files Report", + "reports": "報告", + "rulesReportTitle": "Rules Report", + "copy-swimlane": "複製泳道流程圖", + "copySwimlanePopup-title": "複製泳道流程圖", + "display-card-creator": "顯示卡片創建者", + "wait-spinner": "Wait Spinner", + "Bounce": "Bounce Wait Spinner", + "Cube": "Cube Wait Spinner", + "Cube-Grid": "Cube-Grid Wait Spinner", + "Dot": "Dot Wait Spinner", + "Double-Bounce": "Double Bounce Wait Spinner", + "Rotateplane": "Rotateplane Wait Spinner", + "Scaleout": "Scaleout Wait Spinner", + "Wave": "Wave Wait Spinner", + "maximize-card": "Maximize Card", + "minimize-card": "Minimize Card", + "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", + "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it" } \ No newline at end of file diff --git a/models/activities.js b/models/activities.js index 568859a9d..5fb01bd68 100644 --- a/models/activities.js +++ b/models/activities.js @@ -117,9 +117,17 @@ if (Meteor.isServer) { // No need send notification to user of activity // participants = _.union(participants, [activity.userId]); const user = activity.user(); - params.user = user.getName(); - params.userEmails = user.emails; - params.userId = activity.userId; + if (user) { + if (user.getName()) { + params.user = user.getName(); + } + if (user.emails) { + params.userEmails = user.emails; + } + if (activity.userId) { + params.userId = activity.userId; + } + } } if (activity.boardId) { if (board.title.length > 0) { @@ -222,18 +230,30 @@ if (Meteor.isServer) { } if (activity.checklistId) { const checklist = activity.checklist(); - params.checklist = checklist.title; + if (checklist) { + if (checklist.title) { + params.checklist = checklist.title; + } + } } if (activity.checklistItemId) { const checklistItem = activity.checklistItem(); - params.checklistItem = checklistItem.title; + if (checklistItem) { + if (checklistItem.title) { + params.checklistItem = checklistItem.title; + } + } } if (activity.customFieldId) { const customField = activity.customField(); - params.customField = customField.name; - params.customFieldValue = Activities.findOne({ - customFieldId: customField._id, - }).value; + if (customField) { + if (customField.name) { + params.customField = customField.name; + } + if (activity.value) { + params.customFieldValue = activity.value; + } + } } // Label activity did not work yet, unable to edit labels when tried this. //if (activity.labelId) { @@ -254,17 +274,19 @@ if (Meteor.isServer) { if (value) params[key] = value; }); if (board) { - const BIGEVENTS = process.env.BIGEVENTS_PATTERN || 'due'; // if environment BIGEVENTS_PATTERN is set or default, any activityType matching it is important - try { - const atype = activity.activityType; - if (new RegExp(BIGEVENTS).exec(atype)) { - watchers = _.union( - watchers, - board.activeMembers().map(member => member.userId), - ); // notify all active members for important events system defined or default to all activity related to due date + const BIGEVENTS = process.env.BIGEVENTS_PATTERN; // if environment BIGEVENTS_PATTERN is set, any activityType matching it is important + if (BIGEVENTS) { + try { + const atype = activity.activityType; + if (new RegExp(BIGEVENTS).exec(atype)) { + watchers = _.union( + watchers, + board.activeMembers().map(member => member.userId), + ); // notify all active members for important events + } + } catch (e) { + // passed env var BIGEVENTS_PATTERN is not a valid regex } - } catch (e) { - // passed env var BIGEVENTS_PATTERN is not a valid regex } const watchingUsers = _.pluck( @@ -282,7 +304,10 @@ if (Meteor.isServer) { ); } Notifications.getUsers(watchers).forEach(user => { - Notifications.notify(user, title, description, params); + // don't notify a user of their own behavior + if (user._id !== userId) { + Notifications.notify(user, title, description, params); + } }); const integrations = Integrations.find({ diff --git a/models/attachments.js b/models/attachments.js index 3fe1d745e..f0d6ba8e4 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -1,3 +1,8 @@ +export const AttachmentStorage = new Mongo.Collection( + 'cfs_gridfs.attachments.files', +); +export const AvatarStorage = new Mongo.Collection('cfs_gridfs.avatars.files'); + const localFSStore = process.env.ATTACHMENTS_STORE_PATH; const storeName = 'attachments'; const defaultStoreOptions = { diff --git a/models/avatars.js b/models/avatars.js index 2fda031dd..e823dfea5 100644 --- a/models/avatars.js +++ b/models/avatars.js @@ -1,7 +1,7 @@ Avatars = new FS.Collection('avatars', { stores: [new FS.Store.GridFS('avatars')], filter: { - maxSize: 72000, + maxSize: 520000, allow: { contentTypes: ['image/*'], }, diff --git a/models/boards.js b/models/boards.js index 35ee1a36c..e0a86fd1a 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,3 +1,12 @@ +import { + ALLOWED_BOARD_COLORS, + ALLOWED_COLORS, + TYPE_BOARD, + TYPE_TEMPLATE_BOARD, + TYPE_TEMPLATE_CONTAINER, +} from '/config/const'; + +const escapeForRegex = require('escape-string-regexp'); Boards = new Mongo.Collection('boards'); /** @@ -18,18 +27,14 @@ Boards.attachSchema( type: String, // eslint-disable-next-line consistent-return autoValue() { - // XXX We need to improve slug management. Only the id should be necessary - // to identify a board in the code. - // XXX If the board title is updated, the slug should also be updated. // In some cases (Chinese and Japanese for instance) the `getSlug` function // return an empty string. This is causes bugs in our application so we set // a default slug in this case. - if (this.isInsert && !this.isSet) { + // Improvment would be to change client URL after slug is changed + const title = this.field('title'); + if (title.isSet && !this.isSet) { let slug = 'board'; - const title = this.field('title'); - if (title.isSet) { - slug = getSlug(title.value) || slug; - } + slug = getSlug(title.value) || slug; return slug; } }, @@ -46,6 +51,13 @@ Boards.attachSchema( } }, }, + archivedAt: { + /** + * Latest archiving time of the board + */ + type: Date, + optional: true, + }, createdAt: { /** * Creation time of the board @@ -97,6 +109,8 @@ Boards.attachSchema( * List of labels attached to a board */ type: [Object], + optional: true, + /* Commented out, so does not create labels to new boards. // eslint-disable-next-line consistent-return autoValue() { if (this.isInsert && !this.isSet) { @@ -110,6 +124,7 @@ Boards.attachSchema( })); } }, + */ }, 'labels.$._id': { /** @@ -140,32 +155,7 @@ Boards.attachSchema( * `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo` */ type: String, - allowedValues: [ - 'green', - 'yellow', - 'orange', - 'red', - 'purple', - 'blue', - 'sky', - 'lime', - 'pink', - 'black', - 'silver', - 'peachpuff', - 'crimson', - 'plum', - 'darkgreen', - 'slateblue', - 'magenta', - 'gold', - 'navy', - 'gray', - 'saddlebrown', - 'paleturquoise', - 'mistyrose', - 'indigo', - ], + allowedValues: ALLOWED_COLORS, }, // XXX We might want to maintain more informations under the member sub- // documents like de-normalized meta-data (the date the member joined the @@ -237,29 +227,66 @@ Boards.attachSchema( type: String, allowedValues: ['public', 'private'], }, + orgs: { + /** + * the list of organizations that a board belongs to + */ + type: [Object], + optional: true, + }, + 'orgs.$.orgId':{ + /** + * The uniq ID of the organization + */ + type: String, + }, + 'orgs.$.orgDisplayName':{ + /** + * The display name of the organization + */ + type: String, + }, + 'orgs.$.isActive': { + /** + * Is the organization active? + */ + type: Boolean, + }, + teams: { + /** + * the list of teams that a board belongs to + */ + type: [Object], + optional: true, + }, + 'teams.$.teamId':{ + /** + * The uniq ID of the team + */ + type: String, + }, + 'teams.$.teamDisplayName':{ + /** + * The display name of the team + */ + type: String, + }, + 'teams.$.isActive': { + /** + * Is the team active? + */ + type: Boolean, + }, color: { /** * The color of the board. */ type: String, - allowedValues: [ - 'belize', - 'nephritis', - 'pomegranate', - 'pumpkin', - 'wisteria', - 'moderatepink', - 'strongcyan', - 'limegreen', - 'midnight', - 'dark', - 'relax', - 'corteza', - ], + allowedValues: ALLOWED_BOARD_COLORS, // eslint-disable-next-line consistent-return autoValue() { if (this.isInsert && !this.isSet) { - return Boards.simpleSchema()._schema.color.allowedValues[0]; + return ALLOWED_BOARD_COLORS[0]; } }, }, @@ -364,6 +391,14 @@ Boards.attachSchema( defaultValue: true, }, + allowsCreator: { + /** + * Does the board allow creator? + */ + type: Boolean, + defaultValue: true, + }, + allowsAssignee: { /** * Does the board allows assignee? @@ -388,6 +423,14 @@ Boards.attachSchema( defaultValue: true, }, + allowsCardSortingByNumber: { + /** + * Does the board allows card sorting by number? + */ + type: Boolean, + defaultValue: true, + }, + allowsAssignedBy: { /** * Does the board allows requested by? @@ -489,9 +532,19 @@ Boards.attachSchema( type: { /** * The type of board + * possible values: board, template-board, template-container */ type: String, - defaultValue: 'board', + defaultValue: TYPE_BOARD, + allowedValues: [TYPE_BOARD, TYPE_TEMPLATE_BOARD, TYPE_TEMPLATE_CONTAINER], + }, + sort: { + /** + * Sort value + */ + type: Number, + decimal: true, + defaultValue: -1, }, }), ); @@ -500,6 +553,8 @@ Boards.helpers({ copy() { const oldId = this._id; delete this._id; + delete this.slug; + this.title = this.copyTitle(); const _id = Boards.insert(this); // Copy all swimlanes in board @@ -510,7 +565,58 @@ Boards.helpers({ swimlane.type = 'swimlane'; swimlane.copy(_id); }); + + // copy custom field definitions + const cfMap = {}; + CustomFields.find({ boardIds: oldId }).forEach(cf => { + const id = cf._id; + delete cf._id; + cf.boardIds = [_id]; + cfMap[id] = CustomFields.insert(cf); + }); + Cards.find({ boardId: _id }).forEach(card => { + Cards.update(card._id, { + $set: { + customFields: card.customFields.map(cf => { + cf._id = cfMap[cf._id]; + return cf; + }), + }, + }); + }); + + // copy rules, actions, and triggers + const actionsMap = {}; + Actions.find({ boardId: oldId }).forEach(action => { + const id = action._id; + delete action._id; + action.boardId = _id; + actionsMap[id] = Actions.insert(action); + }); + const triggersMap = {}; + Triggers.find({ boardId: oldId }).forEach(trigger => { + const id = trigger._id; + delete trigger._id; + trigger.boardId = _id; + triggersMap[id] = Triggers.insert(trigger); + }); + Rules.find({ boardId: oldId }).forEach(rule => { + delete rule._id; + rule.boardId = _id; + rule.actionId = actionsMap[rule.actionId]; + rule.triggerId = triggersMap[rule.triggerId]; + Rules.insert(rule); + }); }, + /** + * Return a unique title based on the current title + * + * @returns {string|null} + */ + copyTitle() { + return Boards.uniqueTitle(this.title); + }, + /** * Is supplied user authorized to view this board? */ @@ -639,6 +745,23 @@ Boards.helpers({ return _.where(this.members, { isActive: true }); }, + + activeOrgs() { + return _.where(this.orgs, { isActive: true }); + }, + + // hasNotAnyOrg(){ + // return this.orgs === undefined || this.orgs.length <= 0; + // }, + + activeTeams() { + return _.where(this.teams, { isActive: true }); + }, + + // hasNotAnyTeam(){ + // return this.teams === undefined || this.teams.length <= 0; + // }, + activeAdmins() { return _.where(this.members, { isActive: true, isAdmin: true }); }, @@ -708,6 +831,9 @@ Boards.helpers({ absoluteUrl() { return FlowRouter.url('board', { id: this._id, slug: this.slug }); }, + originRelativeUrl() { + return FlowRouter.path('board', { id: this._id, slug: this.slug }); + }, colorClass() { return `board-color-${this.color}`; @@ -980,7 +1106,7 @@ Boards.helpers({ Boards.mutations({ archive() { - return { $set: { archived: true } }; + return { $set: { archived: true, archivedAt: new Date() } }; }, restore() { @@ -1115,6 +1241,10 @@ Boards.mutations({ return { $set: { allowsSubtasks } }; }, + setAllowsCreator(allowsCreator) { + return { $set: { allowsCreator } }; + }, + setAllowsMembers(allowsMembers) { return { $set: { allowsMembers } }; }, @@ -1135,6 +1265,10 @@ Boards.mutations({ return { $set: { allowsRequestedBy } }; }, + setAllowsCardSortingByNumber(allowsCardSortingByNumber) { + return { $set: { allowsCardSortingByNumber } }; + }, + setAllowsAttachments(allowsAttachments) { return { $set: { allowsAttachments } }; }, @@ -1186,6 +1320,10 @@ Boards.mutations({ setPresentParentTask(presentParentTask) { return { $set: { presentParentTask } }; }, + + move(sortIndex) { + return { $set: { sort: sortIndex } }; + }, }); function boardRemover(userId, doc) { @@ -1196,6 +1334,81 @@ function boardRemover(userId, doc) { ); } +Boards.uniqueTitle = title => { + const m = title.match( + new RegExp('^(?<title>.*?)\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)'), + ); + const base = escapeForRegex(m.groups.title); + let num = 0; + Boards.find({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) }).forEach( + board => { + const m = board.title.match( + new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'), + ); + if (m) { + const n = parseInt(m.groups.num, 10); + num = num < n ? n : num; + } + }, + ); + + if (num > 0) { + return `${base} [${num + 1}]`; + } + + return title; +}; + +Boards.userSearch = ( + userId, + selector = {}, + projection = {}, + // includeArchived = false, +) => { + // if (!includeArchived) { + // selector.archived = false; + // } + selector.$or = [{ permission: 'public' }]; + + if (userId) { + selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } }); + } + return Boards.find(selector, projection); +}; + +Boards.userBoards = (userId, archived = false, selector = {}) => { + if (typeof archived === 'boolean') { + selector.archived = archived; + } + if (!selector.type) { + selector.type = 'board'; + } + + selector.$or = [{ permission: 'public' }]; + if (userId) { + selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } }); + } + return Boards.find(selector); +}; + +Boards.userBoardIds = (userId, archived = false, selector = {}) => { + return Boards.userBoards(userId, archived, selector).map(board => { + return board._id; + }); +}; + +Boards.colorMap = () => { + const colors = {}; + for (const color of Boards.labelColors()) { + colors[TAPi18n.__(`color-${color}`)] = color; + } + return colors; +}; + +Boards.labelColors = () => { + return ALLOWED_COLORS; +}; + if (Meteor.isServer) { Boards.allow({ insert: Meteor.userId, @@ -1204,6 +1417,14 @@ if (Meteor.isServer) { fetch: ['members'], }); + // All logged in users are allowed to reorder boards by dragging at All Boards page and Public Boards page. + Boards.allow({ + update(userId, board, fieldNames) { + return _.contains(fieldNames, 'sort'); + }, + fetch: [], + }); + // The number of users that have starred this board is managed by trusted code // and the user is not allowed to update it Boards.deny({ @@ -1265,6 +1486,31 @@ if (Meteor.isServer) { }, }); }, + myLabelNames() { + let names = []; + Boards.userBoards(Meteor.userId()).forEach(board => { + // Only return labels when they exist. + if (board.labels !== undefined) { + names = names.concat( + board.labels + .filter(label => !!label.name) + .map(label => { + return label.name; + }), + ); + } else { + return []; + } + }); + return _.uniq(names).sort(); + }, + myBoardNames() { + return _.uniq( + Boards.userBoards(Meteor.userId()).map(board => { + return board.title; + }), + ).sort(); + }, }); Meteor.methods({ @@ -1280,9 +1526,38 @@ if (Meteor.isServer) { } else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-doesNotExist'); }, + setBoardOrgs(boardOrgsArray, currBoardId){ + check(boardOrgsArray, Array); + check(currBoardId, String); + Boards.update(currBoardId, { + $set: { + orgs: boardOrgsArray, + }, + }); + }, + setBoardTeams(boardTeamsArray, currBoardId){ + check(boardTeamsArray, Array); + check(currBoardId, String); + Boards.update(currBoardId, { + $set: { + teams: boardTeamsArray, + }, + }); + }, }); } +// Insert new board at last position in sort order. +Boards.before.insert((userId, doc) => { + const lastBoard = Boards.findOne( + { sort: { $exists: true } }, + { sort: { sort: -1 } }, + ); + if (lastBoard && typeof lastBoard.sort !== 'undefined') { + doc.sort = lastBoard.sort + 1; + } +}); + if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board Meteor.startup(() => { @@ -1466,7 +1741,7 @@ if (Meteor.isServer) { 'members.userId': paramUserId, }, { - sort: ['title'], + sort: { sort: 1 /* boards default sorting */ }, }, ).map(function(board) { return { @@ -1493,10 +1768,16 @@ if (Meteor.isServer) { */ JsonRoutes.add('GET', '/api/boards', function(req, res) { try { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: Boards.find({ permission: 'public' }).map(function(doc) { + data: Boards.find( + { permission: 'public' }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -1511,6 +1792,30 @@ if (Meteor.isServer) { } }); + /** + * @operation get_boards_count + * @summary Get public and private boards count + * + * @return_type {private: integer, public: integer} + */ + JsonRoutes.add('GET', '/api/boards_count', function(req, res) { + try { + Authentication.checkUserId(req.userId); + JsonRoutes.sendResult(res, { + code: 200, + data: { + private: Boards.find({ permission: 'private' }).count(), + public: Boards.find({ permission: 'public' }).count(), + }, + }); + } catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); + /** * @operation get_board * @summary Get the board with that particular ID @@ -1638,7 +1943,8 @@ if (Meteor.isServer) { * @return_type string */ JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const id = req.params.boardId; try { if (req.body.hasOwnProperty('label')) { @@ -1717,6 +2023,41 @@ if (Meteor.isServer) { }); } }); + + //ATTACHMENTS REST API + /** + * @operation get_board_attachments + * @summary Get the list of attachments of a board + * + * @param {string} boardId the board ID + * @return_type [{attachmentId: string, + * attachmentName: string, + * attachmentType: string, + * cardId: string, + * listId: string, + * swimlaneId: string}] + */ + JsonRoutes.add('GET', '/api/boards/:boardId/attachments', function(req, res) { + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); + JsonRoutes.sendResult(res, { + code: 200, + data: Attachments.files + .find({ boardId: paramBoardId }, { fields: { boardId: 0 } }) + .map(function(doc) { + return { + attachmentId: doc._id, + attachmentName: doc.original.name, + attachmentType: doc.original.type, + url: FlowRouter.url(doc.url()), + urlDownload: `${FlowRouter.url(doc.url())}?download=true&token=`, + cardId: doc.cardId, + listId: doc.listId, + swimlaneId: doc.swimlaneId, + }; + }), + }); + }); } export default Boards; diff --git a/models/cardComments.js b/models/cardComments.js index 39477e14a..799b541d8 100644 --- a/models/cardComments.js +++ b/models/cardComments.js @@ -1,3 +1,4 @@ +const escapeForRegex = require('escape-string-regexp'); CardComments = new Mongo.Collection('card_comments'); /** @@ -109,6 +110,28 @@ function commentCreation(userId, doc) { }); } +CardComments.textSearch = (userId, textArray) => { + const selector = { + boardId: { $in: Boards.userBoardIds(userId) }, + $and: [], + }; + + for (const text of textArray) { + selector.$and.push({ text: new RegExp(escapeForRegex(text), 'i') }); + } + + // eslint-disable-next-line no-console + // console.log('cardComments selector:', selector); + + const comments = CardComments.find(selector); + // eslint-disable-next-line no-console + // console.log('count:', comments.count()); + // eslint-disable-next-line no-console + // console.log('cards with comments:', comments.map(com => { return com.cardId })); + + return comments; +}; + if (Meteor.isServer) { // Comments are often fetched within a card, so we create an index to make these // queries more efficient. @@ -145,9 +168,6 @@ if (Meteor.isServer) { listId: card.listId, swimlaneId: card.swimlaneId, }); - }); - - CardComments.after.remove((userId, doc) => { const activity = Activities.findOne({ commentId: doc._id }); if (activity) { Activities.remove(activity._id); @@ -172,8 +192,8 @@ if (Meteor.isServer) { res, ) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCardId = req.params.cardId; JsonRoutes.sendResult(res, { code: 200, @@ -210,8 +230,8 @@ if (Meteor.isServer) { '/api/boards/:boardId/cards/:cardId/comments/:commentId', function(req, res) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCommentId = req.params.commentId; const paramCardId = req.params.cardId; JsonRoutes.sendResult(res, { @@ -246,8 +266,8 @@ if (Meteor.isServer) { '/api/boards/:boardId/cards/:cardId/comments', function(req, res) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCardId = req.params.cardId; const id = CardComments.direct.insert({ userId: req.body.authorId, @@ -292,8 +312,8 @@ if (Meteor.isServer) { '/api/boards/:boardId/cards/:cardId/comments/:commentId', function(req, res) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCommentId = req.params.commentId; const paramCardId = req.params.cardId; CardComments.remove({ diff --git a/models/cards.js b/models/cards.js index eed1b9582..08716e68e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1,3 +1,10 @@ +import { + ALLOWED_COLORS, + TYPE_CARD, + TYPE_LINKED_BOARD, + TYPE_LINKED_CARD, +} from '../config/const'; + Cards = new Mongo.Collection('cards'); // XXX To improve pub/sub performances a card document should include a @@ -26,6 +33,13 @@ Cards.attachSchema( } }, }, + archivedAt: { + /** + * latest archiving date + */ + type: Date, + optional: true, + }, parentId: { /** * ID of the parent card @@ -70,33 +84,7 @@ Cards.attachSchema( color: { type: String, optional: true, - allowedValues: [ - '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', - ], + allowedValues: ALLOWED_COLORS, }, createdAt: { /** @@ -148,10 +136,14 @@ Cards.attachSchema( /** * value attached to the custom field */ - type: Match.OneOf(String, Number, Boolean, Date), + type: Match.OneOf(String, Number, Boolean, Date, [String]), optional: true, defaultValue: '', }, + 'value.$': { + type: String, + optional: true, + }, }), }, dateLastActivity: { @@ -278,7 +270,8 @@ Cards.attachSchema( */ type: Number, decimal: true, - defaultValue: '', + defaultValue: 0, + optional: true, }, subtaskSort: { /** @@ -294,7 +287,8 @@ Cards.attachSchema( * type of the card */ type: String, - defaultValue: 'cardType-card', + defaultValue: TYPE_CARD, + // allowedValues: [TYPE_CARD, TYPE_LINKED_CARD, TYPE_LINKED_BOARD, TYPE_TEMPLATE_CARD], }, linkedId: { /** @@ -304,6 +298,178 @@ Cards.attachSchema( optional: true, defaultValue: '', }, + vote: { + /** + * vote object, see below + */ + type: Object, + optional: true, + }, + 'vote.question': { + type: String, + defaultValue: '', + }, + 'vote.positive': { + /** + * list of members (user IDs) + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'vote.negative': { + /** + * list of members (user IDs) + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'vote.end': { + type: Date, + optional: true, + defaultValue: null, + }, + 'vote.public': { + type: Boolean, + defaultValue: false, + }, + 'vote.allowNonBoardMembers': { + type: Boolean, + defaultValue: false, + }, + poker: { + /** + * poker object, see below + */ + type: Object, + optional: true, + }, + 'poker.question': { + type: Boolean, + defaultValue: false, + }, + 'poker.one': { + /** + * poker card one + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.two': { + /** + * poker card two + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.three': { + /** + * poker card three + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.five': { + /** + * poker card five + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.eight': { + /** + * poker card eight + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.thirteen': { + /** + * poker card thirteen + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.twenty': { + /** + * poker card twenty + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.forty': { + /** + * poker card forty + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.oneHundred': { + /** + * poker card oneHundred + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.unsure': { + /** + * poker card unsure + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.end': { + type: Date, + optional: true, + defaultValue: null, + }, + 'poker.allowNonBoardMembers': { + type: Boolean, + defaultValue: false, + }, + 'poker.estimation': { + /** + * poker estimation value + */ + type: Number, + optional: true, + }, + targetId_gantt: { + /** + * ID of card which is the child link in gantt view + */ + type: [String], + optional: true, + defaultValue: [], + }, + linkType_gantt: { + /** + * ID of card which is the parent link in gantt view + */ + type: [Number], + decimal: false, + optional: true, + defaultValue: [], + }, + linkId_gantt: { + /** + * ID of card which is the parent link in gantt view + */ + type: [String], + optional: true, + defaultValue: [], + }, }), ); @@ -311,8 +477,14 @@ Cards.allow({ insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + + update(userId, doc, fields) { + // Allow board members or logged in users if only vote get's changed + return ( + allowIsBoardMember(userId, Boards.findOne(doc.boardId)) || + (_.isEqual(fields, ['vote', 'modifiedAt', 'dateLastActivity']) && + !!userId) + ); }, remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); @@ -321,45 +493,79 @@ Cards.allow({ }); Cards.helpers({ + // Gantt https://github.com/wekan/wekan/issues/2870#issuecomment-857171127 + setGanttTargetId(sourceId, targetId, linkType, linkId){ + return Cards.update({ _id: sourceId}, { + $push: { + targetId_gantt: targetId, + linkType_gantt : linkType, + linkId_gantt: linkId + } + }); + }, + + removeGanttTargetId(sourceId, targetId, linkType, linkId){ + return Cards.update({ _id: sourceId}, { + $pull: { + targetId_gantt: targetId, + linkType_gantt : linkType, + linkId_gantt: linkId + } + }); + }, + + mapCustomFieldsToBoard(boardId) { + // Map custom fields to new board + return this.customFields.map(cf => { + const oldCf = CustomFields.findOne(cf._id); + const newCf = CustomFields.findOne({ + boardIds: boardId, + name: oldCf.name, + type: oldCf.type, + }); + if (newCf) { + cf._id = newCf._id; + } else if (!_.contains(oldCf.boardIds, boardId)) { + oldCf.addBoard(boardId); + } + return cf; + }); + }, + copy(boardId, swimlaneId, listId) { - const oldBoard = Boards.findOne(this.boardId); - const oldBoardLabels = oldBoard.labels; - // Get old label names - const oldCardLabels = _.pluck( - _.filter(oldBoardLabels, label => { - return _.contains(this.labelIds, label._id); - }), - 'name', - ); - - const newBoard = Boards.findOne(boardId); - const newBoardLabels = newBoard.labels; - const newCardLabels = _.pluck( - _.filter(newBoardLabels, label => { - return _.contains(oldCardLabels, label.name); - }), - '_id', - ); - const oldId = this._id; const oldCard = Cards.findOne(oldId); - // Copy Custom Fields - if (oldBoard._id !== boardId) { - CustomFields.find({ - _id: { - $in: oldCard.customFields.map(cf => { - return cf._id; - }), - }, - }).forEach(cf => { - if (!_.contains(cf.boardIds, boardId)) cf.addBoard(boardId); - }); + // we must only copy the labels and custom fields if the target board + // differs from the source board + if (this.boardId !== boardId) { + const oldBoard = Boards.findOne(this.boardId); + const oldBoardLabels = oldBoard.labels; + + // Get old label names + const oldCardLabels = _.pluck( + _.filter(oldBoardLabels, label => { + return _.contains(this.labelIds, label._id); + }), + 'name', + ); + + const newBoard = Boards.findOne(boardId); + const newBoardLabels = newBoard.labels; + const newCardLabels = _.pluck( + _.filter(newBoardLabels, label => { + return _.contains(oldCardLabels, label.name); + }), + '_id', + ); + // now set the new label ids + delete this.labelIds; + this.labelIds = newCardLabels; + + this.customFields = this.mapCustomFieldsToBoard(newBoard._id); } delete this._id; - delete this.labelIds; - this.labelIds = newCardLabels; this.boardId = boardId; this.swimlaneId = swimlaneId; this.listId = listId; @@ -392,14 +598,72 @@ Cards.helpers({ return _id; }, + link(boardId, swimlaneId, listId) { + // TODO is there a better method to create a deepcopy? + linkCard = JSON.parse(JSON.stringify(this)); + // TODO is this how it is meant to be? + linkCard.linkedId = linkCard.linkedId || linkCard._id; + linkCard.boardId = boardId; + linkCard.swimlaneId = swimlaneId; + linkCard.listId = listId; + linkCard.type = 'cardType-linkedCard'; + delete linkCard._id; + // TODO shall we copy the labels for a linked card?! + delete linkCard.labelIds; + return Cards.insert(linkCard); + }, + list() { return Lists.findOne(this.listId); }, + swimlane() { + return Swimlanes.findOne(this.swimlaneId); + }, + board() { return Boards.findOne(this.boardId); }, + getList() { + const list = this.list(); + if (!list) { + return { + _id: this.listId, + title: 'Undefined List', + archived: false, + colorClass: '', + }; + } + return list; + }, + + getSwimlane() { + const swimlane = this.swimlane(); + if (!swimlane) { + return { + _id: this.swimlaneId, + title: 'Undefined Swimlane', + archived: false, + colorClass: '', + }; + } + return swimlane; + }, + + getBoard() { + const board = this.board(); + if (!board) { + return { + _id: this.boardId, + title: 'Undefined Board', + archived: false, + colorClass: '', + }; + } + return board; + }, + labels() { const boardLabels = this.board().labels; const cardLabels = _.filter(boardLabels, label => { @@ -469,6 +733,7 @@ Cards.helpers({ }, cover() { + if (!this.coverId) return false; const cover = Attachments.findOne(this.coverId); // if we return a cover before it is fully stored, we will get errors when we try to display it // todo XXX we could return a default "upload pending" image in the meantime? @@ -530,37 +795,32 @@ Cards.helpers({ ); }, - allSubtasks() { - return Cards.find( - { - parentId: this._id, - archived: false, - }, - { - sort: { - sort: 1, - }, - }, - ); - }, - - subtasksCount() { - return Cards.find({ - parentId: this._id, - archived: false, - }).count(); - }, - - subtasksFinishedCount() { + subtasksFinished() { return Cards.find({ parentId: this._id, archived: true, - }).count(); + }); }, - subtasksFinished() { - const finishCount = this.subtasksFinishedCount(); - return finishCount > 0 && this.subtasksCount() === finishCount; + allSubtasks() { + return Cards.find({ + parentId: this._id, + }); + }, + + subtasksCount() { + const subtasks = this.subtasks(); + return subtasks.count(); + }, + + subtasksFinishedCount() { + const subtasksArchived = this.subtasksFinished(); + return subtasksArchived.count(); + }, + + allSubtasksCount() { + const allSubtasks = this.allSubtasks(); + return allSubtasks.count(); }, allowsSubtasks() { @@ -577,10 +837,12 @@ Cards.helpers({ const definitions = CustomFields.find({ boardIds: { $in: [this.boardId] }, }).fetch(); - + if (!definitions) { + return {}; + } // match right definition to each field if (!this.customFields) return []; - return this.customFields.map(customField => { + const ret = this.customFields.map(customField => { const definition = definitions.find(definition => { return definition._id === customField._id; }); @@ -606,6 +868,16 @@ Cards.helpers({ definition, }; }); + // at linked cards custom fields definition is not found + ret.sort( + (a, b) => + a.definition !== undefined && + b.definition !== undefined && + a.definition.name !== undefined && + b.definition.name !== undefined && + a.definition.name.localeCompare(b.definition.name), + ); + return ret; }, colorClass() { @@ -621,6 +893,14 @@ Cards.helpers({ cardId: this._id, }); }, + originRelativeUrl() { + const board = this.board(); + return FlowRouter.path('card', { + boardId: board._id, + slug: board.slug, + cardId: this._id, + }); + }, canBeRestored() { const list = Lists.findOne({ @@ -747,12 +1027,20 @@ Cards.helpers({ getMembers() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.members; + if (card === undefined) { + return null; + } else { + return card.members; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.activeMembers().map(member => { - return member.userId; - }); + if (board === undefined) { + return null; + } else { + return board.activeMembers().map(member => { + return member.userId; + }); + } } else { return this.members; } @@ -761,12 +1049,20 @@ Cards.helpers({ getAssignees() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.assignees; + if (card === undefined) { + return null; + } else { + return card.assignees; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.activeMembers().map(assignee => { - return assignee.userId; - }); + if (board === undefined) { + return null; + } else { + return board.activeMembers().map(assignee => { + return assignee.userId; + }); + } } else { return this.assignees; } @@ -856,7 +1152,11 @@ Cards.helpers({ getReceived() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.receivedAt; + if (card === undefined) { + return null; + } else { + return card.receivedAt; + } } else { return this.receivedAt; } @@ -873,10 +1173,18 @@ Cards.helpers({ getStart() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.startAt; + if (card === undefined) { + return null; + } else { + return card.startAt; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.startAt; + if (board === undefined) { + return null; + } else { + return board.startAt; + } } else { return this.startAt; } @@ -895,10 +1203,18 @@ Cards.helpers({ getDue() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.dueAt; + if (card === undefined) { + return null; + } else { + return card.dueAt; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.dueAt; + if (board === undefined) { + return null; + } else { + return board.dueAt; + } } else { return this.dueAt; } @@ -917,10 +1233,18 @@ Cards.helpers({ getEnd() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.endAt; + if (card === undefined) { + return null; + } else { + return card.endAt; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.endAt; + if (board === undefined) { + return null; + } else { + return board.endAt; + } } else { return this.endAt; } @@ -939,10 +1263,18 @@ Cards.helpers({ getIsOvertime() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.isOvertime; + if (card === undefined) { + return null; + } else { + return card.isOvertime; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.isOvertime; + if (board === undefined) { + return null; + } else { + return board.isOvertime; + } } else { return this.isOvertime; } @@ -961,10 +1293,18 @@ Cards.helpers({ getSpentTime() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.spentTime; + if (card === undefined) { + return null; + } else { + return card.spentTime; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.spentTime; + if (board === undefined) { + return null; + } else { + return board.spentTime; + } } else { return this.spentTime; } @@ -980,6 +1320,303 @@ Cards.helpers({ } }, + getVoteQuestion() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.vote) { + return card.vote.question; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.vote) { + return board.vote.question; + } else { + return null; + } + } else if (this.vote) { + return this.vote.question; + } else { + return null; + } + }, + + getVotePublic() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.vote) { + return card.vote.public; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.vote) { + return board.vote.public; + } else { + return null; + } + } else if (this.vote) { + return this.vote.public; + } else { + return null; + } + }, + + getVoteEnd() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.vote) { + return card.vote.end; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.vote) { + return board.vote.end; + } else { + return null; + } + } else if (this.vote) { + return this.vote.end; + } else { + return null; + } + }, + expiredVote() { + let end = this.getVoteEnd(); + if (end) { + end = moment(end); + return end.isBefore(new Date()); + } + return false; + }, + voteMemberPositive() { + if (this.vote && this.vote.positive) + return Users.find({ _id: { $in: this.vote.positive } }); + return []; + }, + + voteMemberNegative() { + if (this.vote && this.vote.negative) + return Users.find({ _id: { $in: this.vote.negative } }); + return []; + }, + voteState() { + const userId = Meteor.userId(); + let state; + if (this.vote) { + if (this.vote.positive) { + state = _.contains(this.vote.positive, userId); + if (state === true) return true; + } + if (this.vote.negative) { + state = _.contains(this.vote.negative, userId); + if (state === true) return false; + } + } + return null; + }, + + getPokerQuestion() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.poker) { + return card.poker.question; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.poker) { + return board.poker.question; + } else { + return null; + } + } else if (this.poker) { + return this.poker.question; + } else { + return null; + } + }, + + getPokerEstimation() { + if (this.poker) { + return this.poker.estimation; + } else { + return null; + } + }, + + getPokerEnd() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.poker) { + return card.poker.end; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.poker) { + return board.poker.end; + } else { + return null; + } + } else if (this.poker) { + return this.poker.end; + } else { + return null; + } + }, + expiredPoker() { + let end = this.getPokerEnd(); + if (end) { + end = moment(end); + return end.isBefore(new Date()); + } + return false; + }, + pokerMemberOne() { + if (this.poker && this.poker.one) + return Users.find({ _id: { $in: this.poker.one } }); + return []; + }, + pokerMemberTwo() { + if (this.poker && this.poker.two) + return Users.find({ _id: { $in: this.poker.two } }); + return []; + }, + pokerMemberThree() { + if (this.poker && this.poker.three) + return Users.find({ _id: { $in: this.poker.three } }); + return []; + }, + pokerMemberFive() { + if (this.poker && this.poker.five) + return Users.find({ _id: { $in: this.poker.five } }); + return []; + }, + pokerMemberEight() { + if (this.poker && this.poker.eight) + return Users.find({ _id: { $in: this.poker.eight } }); + return []; + }, + pokerMemberThirteen() { + if (this.poker && this.poker.thirteen) + return Users.find({ _id: { $in: this.poker.thirteen } }); + return []; + }, + pokerMemberTwenty() { + if (this.poker && this.poker.twenty) + return Users.find({ _id: { $in: this.poker.twenty } }); + return []; + }, + pokerMemberForty() { + if (this.poker && this.poker.forty) + return Users.find({ _id: { $in: this.poker.forty } }); + return []; + }, + pokerMemberOneHundred() { + if (this.poker && this.poker.oneHundred) + return Users.find({ _id: { $in: this.poker.oneHundred } }); + return []; + }, + pokerMemberUnsure() { + if (this.poker && this.poker.unsure) + return Users.find({ _id: { $in: this.poker.unsure } }); + return []; + }, + pokerState() { + const userId = Meteor.userId(); + let state; + if (this.poker) { + if (this.poker.one) { + state = _.contains(this.poker.one, userId); + if (state === true) { + return 'one'; + } + } + if (this.poker.two) { + state = _.contains(this.poker.two, userId); + if (state === true) { + return 'two'; + } + } + if (this.poker.three) { + state = _.contains(this.poker.three, userId); + if (state === true) { + return 'three'; + } + } + if (this.poker.five) { + state = _.contains(this.poker.five, userId); + if (state === true) { + return 'five'; + } + } + if (this.poker.eight) { + state = _.contains(this.poker.eight, userId); + if (state === true) { + return 'eight'; + } + } + if (this.poker.thirteen) { + state = _.contains(this.poker.thirteen, userId); + if (state === true) { + return 'thirteen'; + } + } + if (this.poker.twenty) { + state = _.contains(this.poker.twenty, userId); + if (state === true) { + return 'twenty'; + } + } + if (this.poker.forty) { + state = _.contains(this.poker.forty, userId); + if (state === true) { + return 'forty'; + } + } + if (this.poker.oneHundred) { + state = _.contains(this.poker.oneHundred, userId); + if (state === true) { + return 'oneHundred'; + } + } + if (this.poker.unsure) { + state = _.contains(this.poker.unsure, userId); + if (state === true) { + return 'unsure'; + } + } + } + return null; + }, + getId() { if (this.isLinked()) { return this.linkedId; @@ -991,10 +1628,20 @@ Cards.helpers({ getTitle() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.title; + if (card === undefined) { + return null; + } else { + return card.title; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.title; + if (board === undefined) { + return null; + } else { + return board.title; + } + } else if (this.title === undefined) { + return null; } else { return this.title; } @@ -1003,14 +1650,29 @@ Cards.helpers({ getBoardTitle() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } const board = Boards.findOne({ _id: card.boardId }); - return board.title; + if (board === undefined) { + return null; + } else { + return board.title; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.title; + if (board === undefined) { + return null; + } else { + return board.title; + } } else { const board = Boards.findOne({ _id: this.boardId }); - return board.title; + if (board === undefined) { + return null; + } else { + return board.title; + } } }, @@ -1027,10 +1689,18 @@ Cards.helpers({ getArchived() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.archived; + if (card === undefined) { + return null; + } else { + return card.archived; + } } else if (this.isLinkedBoard()) { const board = Boards.findOne({ _id: this.linkedId }); - return board.archived; + if (board === undefined) { + return null; + } else { + return board.archived; + } } else { return this.archived; } @@ -1047,7 +1717,11 @@ Cards.helpers({ getRequestedBy() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.requestedBy; + if (card === undefined) { + return null; + } else { + return card.requestedBy; + } } else { return this.requestedBy; } @@ -1064,7 +1738,11 @@ Cards.helpers({ getAssignedBy() { if (this.isLinkedCard()) { const card = Cards.findOne({ _id: this.linkedId }); - return card.assignedBy; + if (card === undefined) { + return null; + } else { + return card.assignedBy; + } } else { return this.assignedBy; } @@ -1073,6 +1751,121 @@ Cards.helpers({ isTemplateCard() { return this.type === 'template-card'; }, + + votePublic() { + if (this.vote) return this.vote.public; + return null; + }, + voteAllowNonBoardMembers() { + if (this.vote) return this.vote.allowNonBoardMembers; + return null; + }, + voteCountNegative() { + if (this.vote && this.vote.negative) return this.vote.negative.length; + return null; + }, + voteCountPositive() { + if (this.vote && this.vote.positive) return this.vote.positive.length; + return null; + }, + voteCount() { + return this.voteCountPositive() + this.voteCountNegative(); + }, + + pokerAllowNonBoardMembers() { + if (this.poker) return this.poker.allowNonBoardMembers; + return null; + }, + pokerCountOne() { + if (this.poker && this.poker.one) return this.poker.one.length; + return null; + }, + pokerCountTwo() { + if (this.poker && this.poker.two) return this.poker.two.length; + return null; + }, + pokerCountThree() { + if (this.poker && this.poker.three) return this.poker.three.length; + return null; + }, + pokerCountFive() { + if (this.poker && this.poker.five) return this.poker.five.length; + return null; + }, + pokerCountEight() { + if (this.poker && this.poker.eight) return this.poker.eight.length; + return null; + }, + pokerCountThirteen() { + if (this.poker && this.poker.thirteen) return this.poker.thirteen.length; + return null; + }, + pokerCountTwenty() { + if (this.poker && this.poker.twenty) return this.poker.twenty.length; + return null; + }, + pokerCountForty() { + if (this.poker && this.poker.forty) return this.poker.forty.length; + return null; + }, + pokerCountOneHundred() { + if (this.poker && this.poker.oneHundred) return this.poker.oneHundred.length; + return null; + }, + pokerCountUnsure() { + if (this.poker && this.poker.unsure) return this.poker.unsure.length; + return null; + }, + pokerCount() { + return ( + this.pokerCountOne() + + this.pokerCountTwo() + + this.pokerCountThree() + + this.pokerCountFive() + + this.pokerCountEight() + + this.pokerCountThirteen() + + this.pokerCountTwenty() + + this.pokerCountForty() + + this.pokerCountOneHundred() + + this.pokerCountUnsure() + ); + }, + pokerWinner() { + const pokerListMaps = []; + let pokerWinnersListMap = []; + if (this.expiredPoker()) { + const one = { count: this.pokerCountOne(), pokerCard: 1 }; + const two = { count: this.pokerCountTwo(), pokerCard: 2 }; + const three = { count: this.pokerCountThree(), pokerCard: 3 }; + const five = { count: this.pokerCountFive(), pokerCard: 5 }; + const eight = { count: this.pokerCountEight(), pokerCard: 8 }; + const thirteen = { count: this.pokerCountThirteen(), pokerCard: 13 }; + const twenty = { count: this.pokerCountTwenty(), pokerCard: 20 }; + const forty = { count: this.pokerCountForty(), pokerCard: 40 }; + const oneHundred = { count: this.pokerCountOneHundred(), pokerCard: 100 }; + const unsure = { count: this.pokerCountUnsure(), pokerCard: 'Unsure' }; + pokerListMaps.push(one); + pokerListMaps.push(two); + pokerListMaps.push(three); + pokerListMaps.push(five); + pokerListMaps.push(eight); + pokerListMaps.push(thirteen); + pokerListMaps.push(twenty); + pokerListMaps.push(forty); + pokerListMaps.push(oneHundred); + pokerListMaps.push(unsure); + + pokerListMaps.sort(function(a, b) { + return b.count - a.count; + }); + const max = pokerListMaps[0].count; + pokerWinnersListMap = pokerListMaps.filter(task => task.count >= max); + pokerWinnersListMap.sort(function(a, b) { + return b.pokerCard - a.pokerCard; + }); + } + return pokerWinnersListMap[0].pokerCard; + }, }); Cards.mutations({ @@ -1091,6 +1884,7 @@ Cards.mutations({ return { $set: { archived: true, + archivedAt: new Date(), }, }; }, @@ -1106,47 +1900,88 @@ Cards.mutations({ }; }, - move(boardId, swimlaneId, listId, sort) { - // Copy Custom Fields - if (this.boardId !== boardId) { - CustomFields.find({ - _id: { - $in: this.customFields.map(cf => { - return cf._id; - }), - }, - }).forEach(cf => { - if (!_.contains(cf.boardIds, boardId)) cf.addBoard(boardId); - }); + moveToEndOfList({ listId } = {}) { + let swimlaneId = this.swimlaneId; + const boardId = this.boardId; + let sortIndex = 0; + + // This should never happen, but there was a bug that was fixed in commit + // ea0239538a68e225c867411a4f3e0d27c158383. + if (!swimlaneId) { + const board = Boards.findOne(boardId); + swimlaneId = board.getDefaultSwimline()._id; } + // Move the minicard to the end of the target list + let parentElementDom = $(`#swimlane-${this.swimlaneId}`).get(0); + if (!parentElementDom) parentElementDom = $(':root'); - // Get label names - const oldBoard = Boards.findOne(this.boardId); - const oldBoardLabels = oldBoard.labels; - const oldCardLabels = _.pluck( - _.filter(oldBoardLabels, label => { - return _.contains(this.labelIds, label._id); - }), - 'name', - ); + const lastCardDom = $(parentElementDom) + .find(`#js-list-${listId} .js-minicard:last`) + .get(0); + if (lastCardDom) sortIndex = Utils.calculateIndex(lastCardDom, null).base; - const newBoard = Boards.findOne(boardId); - const newBoardLabels = newBoard.labels; - const newCardLabelIds = _.pluck( - _.filter(newBoardLabels, label => { - return label.name && _.contains(oldCardLabels, label.name); - }), - '_id', - ); + return this.moveOptionalArgs({ + boardId, + swimlaneId, + listId, + sort: sortIndex, + }); + }, + moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) { + boardId = boardId || this.boardId; + swimlaneId = swimlaneId || this.swimlaneId; + // This should never happen, but there was a bug that was fixed in commit + // ea0239538a68e225c867411a4f3e0d27c158383. + if (!swimlaneId) { + const board = Boards.findOne(boardId); + swimlaneId = board.getDefaultSwimline()._id; + } + listId = listId || this.listId; + if (sort === undefined || sort === null) sort = this.sort; + return this.move(boardId, swimlaneId, listId, sort); + }, + + move(boardId, swimlaneId, listId, sort = null) { const mutatedFields = { boardId, swimlaneId, listId, - sort, - labelIds: newCardLabelIds, }; + if (sort !== null) { + mutatedFields.sort = sort; + } + + // we must only copy the labels and custom fields if the target board + // differs from the source board + if (this.boardId !== boardId) { + // Get label names + const oldBoard = Boards.findOne(this.boardId); + const oldBoardLabels = oldBoard.labels; + const oldCardLabels = _.pluck( + _.filter(oldBoardLabels, label => { + return _.contains(this.labelIds, label._id); + }), + 'name', + ); + + const newBoard = Boards.findOne(boardId); + const newBoardLabels = newBoard.labels; + const newCardLabelIds = _.pluck( + _.filter(newBoardLabels, label => { + return label.name && _.contains(oldCardLabels, label.name); + }), + '_id', + ); + + Object.assign(mutatedFields, { + labelIds: newCardLabelIds, + }); + + mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id); + } + Cards.update(this._id, { $set: mutatedFields, }); @@ -1197,15 +2032,24 @@ Cards.mutations({ assignAssignee(assigneeId) { // If there is not any assignee, allow one assignee, not more. + /* if (this.getAssignees().length === 0) { return { $addToSet: { assignees: assigneeId, }, }; - } else { - return false; - } + */ + // Allow more that one assignee: + // https://github.com/wekan/wekan/issues/3302 + return { + $addToSet: { + assignees: assigneeId, + }, + }; + //} else { + // return false, + //} }, unassignMember(memberId) { @@ -1396,6 +2240,342 @@ Cards.mutations({ }, }; }, + setVoteQuestion(question, publicVote, allowNonBoardMembers) { + return { + $set: { + vote: { + question, + public: publicVote, + allowNonBoardMembers, + positive: [], + negative: [], + }, + }, + }; + }, + unsetVote() { + return { + $unset: { + vote: '', + }, + }; + }, + setVoteEnd(end) { + return { + $set: { 'vote.end': end }, + }; + }, + unsetVoteEnd() { + return { + $unset: { 'vote.end': '' }, + }; + }, + setVote(userId, forIt) { + switch (forIt) { + case true: + // vote for it + return { + $pull: { + 'vote.negative': userId, + }, + $addToSet: { + 'vote.positive': userId, + }, + }; + case false: + // vote against + return { + $pull: { + 'vote.positive': userId, + }, + $addToSet: { + 'vote.negative': userId, + }, + }; + + default: + // Remove votes + return { + $pull: { + 'vote.positive': userId, + 'vote.negative': userId, + }, + }; + } + }, + + setPokerQuestion(question, allowNonBoardMembers) { + return { + $set: { + poker: { + question, + allowNonBoardMembers, + one: [], + two: [], + three: [], + five: [], + eight: [], + thirteen: [], + twenty: [], + forty: [], + oneHundred: [], + unsure: [], + }, + }, + }; + }, + setPokerEstimation(estimation) { + return { + $set: { 'poker.estimation': estimation }, + }; + }, + unsetPokerEstimation() { + return { + $unset: { 'poker.estimation': '' }, + }; + }, + unsetPoker() { + return { + $unset: { + poker: '', + }, + }; + }, + setPokerEnd(end) { + return { + $set: { 'poker.end': end }, + }; + }, + unsetPokerEnd() { + return { + $unset: { 'poker.end': '' }, + }; + }, + setPoker(userId, state) { + switch (state) { + case 'one': + // poker one + return { + $pull: { + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.one': userId, + }, + }; + case 'two': + // poker two + return { + $pull: { + 'poker.one': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.two': userId, + }, + }; + + case 'three': + // poker three + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.three': userId, + }, + }; + + case 'five': + // poker five + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.five': userId, + }, + }; + + case 'eight': + // poker eight + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.eight': userId, + }, + }; + + case 'thirteen': + // poker thirteen + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.thirteen': userId, + }, + }; + + case 'twenty': + // poker twenty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.twenty': userId, + }, + }; + + case 'forty': + // poker forty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.forty': userId, + }, + }; + + case 'oneHundred': + // poker one hundred + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.oneHundred': userId, + }, + }; + + case 'unsure': + // poker unsure + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + }, + $addToSet: { + 'poker.unsure': userId, + }, + }; + + default: + // Remove pokers + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + }; + } + }, + replayPoker() { + return { + $set: { + 'poker.one': [], + 'poker.two': [], + 'poker.three': [], + 'poker.five': [], + 'poker.eight': [], + 'poker.thirteen': [], + 'poker.twelve': [], + 'poker.forty': [], + 'poker.oneHundred': [], + 'poker.unsure': [], + }, + }; + }, }); //FUNCTIONS FOR creation of Activities @@ -1631,6 +2811,8 @@ function cardCustomFields(userId, doc, fieldNames, modifier) { activityType: 'setCustomField', boardId: doc.boardId, cardId: doc._id, + listId: doc.listId, + swimlaneId: doc.swimlaneId, }; Activities.insert(act); } @@ -1841,11 +3023,12 @@ if (Meteor.isServer) { const card = Cards.findOne(doc._id); const list = card.list(); if (list) { - // change list modifiedAt, when user modified the key values in timingaction array, if it's endAt, put the modifiedAt of list back to one year ago for sorting purpose - const modifiedAt = new Date( - new Date(value).getTime() - - (action === 'endAt' ? 365 * 24 * 3600 * 1e3 : 0), - ); // set it as 1 year before + // change list modifiedAt, when user modified the key values in + // timingaction array, if it's endAt, put the modifiedAt of list + // back to one year ago for sorting purpose + const modifiedAt = moment() + .subtract(1, 'year') + .toISOString(); const boardId = list.boardId; Lists.direct.update( { @@ -1914,6 +3097,11 @@ if (Meteor.isServer) { title: doc.title, description: doc.description, listId: doc.listId, + receivedAt: doc.receivedAt, + startAt: doc.startAt, + dueAt: doc.dueAt, + endAt: doc.endAt, + assignees: doc.assignees, }; }), }); @@ -1950,6 +3138,11 @@ if (Meteor.isServer) { _id: doc._id, title: doc.title, description: doc.description, + receivedAt: doc.receivedAt, + startAt: doc.startAt, + dueAt: doc.dueAt, + endAt: doc.endAt, + assignees: doc.assignees, }; }), }); @@ -2024,7 +3217,7 @@ if (Meteor.isServer) { const check = Users.findOne({ _id: req.body.authorId, }); - const members = req.body.members || [req.body.authorId]; + const members = req.body.members; const assignees = req.body.assignees; if (typeof check !== 'undefined') { const id = Cards.direct.insert({ @@ -2082,6 +3275,7 @@ if (Meteor.isServer) { * @param {string} list the list ID of the card * @param {string} cardId the ID of the card * @param {string} [title] the new title of the card + * @param {string} [sort] the new sort value of the card * @param {string} [listId] the new list ID of the card (move operation) * @param {string} [description] the new description of the card * @param {string} [authorId] change the owner of the card @@ -2101,14 +3295,21 @@ if (Meteor.isServer) { * @param {boolean} [isOverTime] the new isOverTime field of the card * @param {string} [customFields] the new customFields value of the card * @param {string} [color] the new color of the card + * @param {Object} [vote] the vote object + * @param {string} vote.question the vote question + * @param {boolean} vote.public show who voted what + * @param {boolean} vote.allowNonBoardMembers allow all logged in users to vote? + * @param {Object} [poker] the poker object + * @param {string} poker.question the vote question + * @param {boolean} poker.allowNonBoardMembers allow all logged in users to vote? * @return_type {_id: string} */ JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCardId = req.params.cardId; const paramListId = req.params.listId; @@ -2128,8 +3329,8 @@ if (Meteor.isServer) { }, ); } - if (req.body.hasOwnProperty('listId')) { - const newParamListId = req.body.listId; + if (req.body.hasOwnProperty('sort')) { + const newSort = req.body.sort; Cards.direct.update( { _id: paramCardId, @@ -2139,22 +3340,10 @@ if (Meteor.isServer) { }, { $set: { - listId: newParamListId, + sort: newSort, }, }, ); - - const card = Cards.findOne({ - _id: paramCardId, - }); - cardMove( - req.body.authorId, - card, - { - fieldName: 'listId', - }, - paramListId, - ); } if (req.body.hasOwnProperty('parentId')) { const newParentId = req.body.parentId; @@ -2200,6 +3389,49 @@ if (Meteor.isServer) { { $set: { color: newColor } }, ); } + if (req.body.hasOwnProperty('vote')) { + const newVote = req.body.vote; + newVote.positive = []; + newVote.negative = []; + if (!newVote.hasOwnProperty('public')) newVote.public = false; + if (!newVote.hasOwnProperty('allowNonBoardMembers')) + newVote.allowNonBoardMembers = false; + + Cards.direct.update( + { + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, + { $set: { vote: newVote } }, + ); + } + if (req.body.hasOwnProperty('poker')) { + const newPoker = req.body.poker; + newPoker.one = []; + newPoker.two = []; + newPoker.three = []; + newPoker.five = []; + newPoker.eight = []; + newPoker.thirteen = []; + newPoker.twenty = []; + newPoker.forty = []; + newPoker.oneHundred = []; + newPoker.unsure = []; + if (!newPoker.hasOwnProperty('allowNonBoardMembers')) + newPoker.allowNonBoardMembers = false; + + Cards.direct.update( + { + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, + { $set: { poker: newPoker } }, + ); + } if (req.body.hasOwnProperty('labelIds')) { let newlabelIds = req.body.labelIds; if (_.isString(newlabelIds)) { @@ -2381,6 +3613,34 @@ if (Meteor.isServer) { { $set: { swimlaneId: newParamSwimlaneId } }, ); } + if (req.body.hasOwnProperty('listId')) { + const newParamListId = req.body.listId; + Cards.direct.update( + { + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, + { + $set: { + listId: newParamListId, + }, + }, + ); + + const card = Cards.findOne({ + _id: paramCardId, + }); + cardMove( + req.body.authorId, + card, + { + fieldName: 'listId', + }, + paramListId, + ); + } JsonRoutes.sendResult(res, { code: 200, data: { @@ -2406,8 +3666,8 @@ if (Meteor.isServer) { 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramListId = req.params.listId; const paramCardId = req.params.cardId; @@ -2428,6 +3688,44 @@ if (Meteor.isServer) { }); }, ); + + /** + * @operation get_cards_by_custom_field + * @summary Get all Cards that matchs a value of a specific custom field + * + * @param {string} boardId the board ID + * @param {string} customFieldId the list ID + * @param {string} customFieldValue the value to look for + * @return_type [{_id: string, + * title: string, + * description: string, + * listId: string, + * swinlaneId: string}] + */ + JsonRoutes.add( + 'GET', + '/api/boards/:boardId/cardsByCustomField/:customFieldId/:customFieldValue', + function(req, res) { + const paramBoardId = req.params.boardId; + const paramCustomFieldId = req.params.customFieldId; + const paramCustomFieldValue = req.params.customFieldValue; + + Authentication.checkBoardAccess(req.userId, paramBoardId); + JsonRoutes.sendResult(res, { + code: 200, + data: Cards.find({ + boardId: paramBoardId, + customFields: { + $elemMatch: { + _id: paramCustomFieldId, + value: paramCustomFieldValue, + }, + }, + archived: false, + }).fetch(), + }); + }, + ); } export default Cards; diff --git a/models/checklistItems.js b/models/checklistItems.js index 7f3ab0953..fb543716b 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -265,7 +265,8 @@ if (Meteor.isServer) { 'GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramItemId = req.params.itemId; const checklistItem = ChecklistItems.findOne({ _id: paramItemId }); if (checklistItem) { @@ -298,14 +299,23 @@ if (Meteor.isServer) { 'PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramItemId = req.params.itemId; + function isTrue(data) { + try { + return data.toLowerCase() === 'true'; + } catch (error) { + return data; + } + } + if (req.body.hasOwnProperty('isFinished')) { ChecklistItems.direct.update( { _id: paramItemId }, - { $set: { isFinished: req.body.isFinished } }, + { $set: { isFinished: isTrue(req.body.isFinished) } }, ); } if (req.body.hasOwnProperty('title')) { @@ -341,7 +351,8 @@ if (Meteor.isServer) { 'DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramItemId = req.params.itemId; ChecklistItems.direct.remove({ _id: paramItemId }); JsonRoutes.sendResult(res, { diff --git a/models/checklists.js b/models/checklists.js index cf73e500e..d02e848c8 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -70,7 +70,9 @@ Checklists.helpers({ this._id = null; this.cardId = newCardId; const newChecklistId = Checklists.insert(this); - ChecklistItems.find({ checklistId: oldChecklistId }).forEach(item => { + ChecklistItems.find({ checklistId: oldChecklistId }).forEach(function( + item, + ) { item._id = null; item.checklistId = newChecklistId; item.cardId = newCardId; @@ -89,6 +91,11 @@ Checklists.helpers({ { sort: ['sort'] }, ); }, + lastItem() { + const allItems = this.items().fetch(); + const ret = allItems[allItems.length - 1]; + return ret; + }, finishedCount() { return ChecklistItems.find({ checklistId: this._id, @@ -197,7 +204,8 @@ if (Meteor.isServer) { 'GET', '/api/boards/:boardId/cards/:cardId/checklists', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCardId = req.params.cardId; const checklists = Checklists.find({ cardId: paramCardId }).map(function( doc, @@ -240,7 +248,8 @@ if (Meteor.isServer) { 'GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramChecklistId = req.params.checklistId; const paramCardId = req.params.cardId; const checklist = Checklists.findOne({ @@ -344,7 +353,8 @@ if (Meteor.isServer) { 'DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function(req, res) { - Authentication.checkUserId(req.userId); + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramChecklistId = req.params.checklistId; Checklists.remove({ _id: paramChecklistId }); JsonRoutes.sendResult(res, { diff --git a/models/csvCreator.js b/models/csvCreator.js new file mode 100644 index 000000000..6228c2ef9 --- /dev/null +++ b/models/csvCreator.js @@ -0,0 +1,395 @@ +import Boards from './boards'; + +export class CsvCreator { + constructor(data) { + // date to be used for timestamps during import + this._nowDate = new Date(); + // index to help keep track of what information a column stores + // each row represents a card + this.fieldIndex = {}; + this.lists = {}; + // Map of members using username => wekanid + this.members = data.membersMapping ? data.membersMapping : {}; + this.swimlane = null; + } + + /** + * If dateString is provided, + * return the Date it represents. + * If not, will return the date when it was first called. + * This is useful for us, as we want all import operations to + * have the exact same date for easier later retrieval. + * + * @param {String} dateString a properly formatted Date + */ + _now(dateString) { + if (dateString) { + return new Date(dateString); + } + if (!this._nowDate) { + this._nowDate = new Date(); + } + return this._nowDate; + } + + _user(wekanUserId) { + if (wekanUserId && this.members[wekanUserId]) { + return this.members[wekanUserId]; + } + return Meteor.userId(); + } + + /** + * Map the header row titles to an index to help assign proper values to the cards' fields + * Valid headers (name of card fields): + * title, description, status, owner, member, label, due date, start date, finish date, created at, updated at + * Some header aliases can also be accepted. + * Headers are NOT case-sensitive. + * + * @param {Array} headerRow array from row of headers of imported CSV/TSV for cards + */ + mapHeadertoCardFieldIndex(headerRow) { + const index = {}; + index.customFields = []; + for (let i = 0; i < headerRow.length; i++) { + switch (headerRow[i].trim().toLowerCase()) { + case 'title': + index.title = i; + break; + case 'description': + index.description = i; + break; + case 'stage': + case 'status': + case 'state': + index.stage = i; + break; + case 'owner': + index.owner = i; + break; + case 'members': + case 'member': + index.members = i; + break; + case 'labels': + case 'label': + index.labels = i; + break; + case 'due date': + case 'deadline': + case 'due at': + index.dueAt = i; + break; + case 'start date': + case 'start at': + index.startAt = i; + break; + case 'finish date': + case 'end at': + index.endAt = i; + break; + case 'creation date': + case 'created at': + index.createdAt = i; + break; + case 'update date': + case 'updated at': + case 'modified at': + case 'modified on': + index.modifiedAt = i; + break; + } + if (headerRow[i].toLowerCase().startsWith('customfield')) { + if (headerRow[i].split('-')[2] === 'dropdown') { + index.customFields.push({ + name: headerRow[i].split('-')[1], + type: headerRow[i].split('-')[2], + options: headerRow[i].split('-')[3].split('/'), + position: i, + }); + } else if (headerRow[i].split('-')[2] === 'currency') { + index.customFields.push({ + name: headerRow[i].split('-')[1], + type: headerRow[i].split('-')[2], + currencyCode: headerRow[i].split('-')[3], + position: i, + }); + } else { + index.customFields.push({ + name: headerRow[i].split('-')[1], + type: headerRow[i].split('-')[2], + position: i, + }); + } + } + } + this.fieldIndex = index; + } + createCustomFields(boardId) { + this.fieldIndex.customFields.forEach(customField => { + let settings = {}; + if (customField.type === 'dropdown') { + settings = { + dropdownItems: customField.options.map(option => { + return { _id: Random.id(6), name: option }; + }), + }; + } else if (customField.type === 'currency') { + settings = { + currencyCode: customField.currencyCode, + }; + } else { + settings = {}; + } + const id = CustomFields.direct.insert({ + name: customField.name, + type: customField.type, + settings, + showOnCard: false, + automaticallyOnCard: false, + alwaysOnCard: false, + showLabelOnMiniCard: false, + boardIds: [boardId], + }); + customField.id = id; + customField.settings = settings; + }); + } + + createBoard(csvData) { + const boardToCreate = { + archived: false, + color: 'belize', + createdAt: this._now(), + labels: [], + members: [ + { + userId: Meteor.userId(), + wekanId: Meteor.userId(), + isActive: true, + isAdmin: true, + isNoComments: false, + isCommentOnly: false, + swimlaneId: false, + }, + ], + modifiedAt: this._now(), + //default is private, should inform user. + permission: 'private', + slug: 'board', + stars: 0, + title: `Imported Board ${this._now()}`, + }; + + // create labels + const labelsToCreate = new Set(); + for (let i = 1; i < csvData.length; i++) { + if (csvData[i][this.fieldIndex.labels]) { + for (const importedLabel of csvData[i][this.fieldIndex.labels].split( + ' ', + )) { + if (importedLabel && importedLabel.length > 0) { + labelsToCreate.add(importedLabel); + } + } + } + } + for (const label of labelsToCreate) { + let labelName, labelColor; + if (label.indexOf('-') > -1) { + labelName = label.split('-')[0]; + labelColor = label.split('-')[1]; + } else { + labelName = label; + } + const labelToCreate = { + _id: Random.id(6), + color: labelColor ? labelColor : 'black', + name: labelName, + }; + boardToCreate.labels.push(labelToCreate); + } + + const boardId = Boards.direct.insert(boardToCreate); + Boards.direct.update(boardId, { + $set: { + modifiedAt: this._now(), + }, + }); + // log activity + Activities.direct.insert({ + activityType: 'importBoard', + boardId, + createdAt: this._now(), + source: { + id: boardId, + system: 'CSV/TSV', + }, + // We attribute the import to current user, + // not the author from the original object. + userId: this._user(), + }); + return boardId; + } + + createSwimlanes(boardId) { + const swimlaneToCreate = { + archived: false, + boardId, + createdAt: this._now(), + title: 'Default', + sort: 1, + }; + const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate); + Swimlanes.direct.update(swimlaneId, { $set: { updatedAt: this._now() } }); + this.swimlane = swimlaneId; + } + + createLists(csvData, boardId) { + let numOfCreatedLists = 0; + for (let i = 1; i < csvData.length; i++) { + const listToCreate = { + archived: false, + boardId, + createdAt: this._now(), + }; + if (csvData[i][this.fieldIndex.stage]) { + const existingList = Lists.find({ + title: csvData[i][this.fieldIndex.stage], + boardId, + }).fetch(); + if (existingList.length > 0) { + continue; + } else { + listToCreate.title = csvData[i][this.fieldIndex.stage]; + } + } else listToCreate.title = `Imported List ${this._now()}`; + + const listId = Lists.direct.insert(listToCreate); + this.lists[csvData[i][this.fieldIndex.stage]] = listId; + numOfCreatedLists++; + Lists.direct.update(listId, { + $set: { + updatedAt: this._now(), + sort: numOfCreatedLists, + }, + }); + } + } + + createCards(csvData, boardId) { + for (let i = 1; i < csvData.length; i++) { + const cardToCreate = { + archived: false, + boardId, + createdAt: + csvData[i][this.fieldIndex.createdAt] !== ' ' || '' + ? this._now(new Date(csvData[i][this.fieldIndex.createdAt])) + : null, + dateLastActivity: this._now(), + description: csvData[i][this.fieldIndex.description], + listId: this.lists[csvData[i][this.fieldIndex.stage]], + swimlaneId: this.swimlane, + sort: -1, + title: csvData[i][this.fieldIndex.title], + userId: this._user(), + startAt: + csvData[i][this.fieldIndex.startAt] !== ' ' || '' + ? this._now(new Date(csvData[i][this.fieldIndex.startAt])) + : null, + dueAt: + csvData[i][this.fieldIndex.dueAt] !== ' ' || '' + ? this._now(new Date(csvData[i][this.fieldIndex.dueAt])) + : null, + endAt: + csvData[i][this.fieldIndex.endAt] !== ' ' || '' + ? this._now(new Date(csvData[i][this.fieldIndex.endAt])) + : null, + spentTime: null, + labelIds: [], + modifiedAt: + csvData[i][this.fieldIndex.modifiedAt] !== ' ' || '' + ? this._now(new Date(csvData[i][this.fieldIndex.modifiedAt])) + : null, + }; + // add the labels + if (csvData[i][this.fieldIndex.labels]) { + const board = Boards.findOne(boardId); + for (const importedLabel of csvData[i][this.fieldIndex.labels].split( + ' ', + )) { + if (importedLabel && importedLabel.length > 0) { + let labelToApply; + if (importedLabel.indexOf('-') === -1) { + labelToApply = board.getLabel(importedLabel, 'black'); + } else { + labelToApply = board.getLabel( + importedLabel.split('-')[0], + importedLabel.split('-')[1], + ); + } + cardToCreate.labelIds.push(labelToApply._id); + } + } + } + // add the members + if (csvData[i][this.fieldIndex.members]) { + const wekanMembers = []; + for (const importedMember of csvData[i][this.fieldIndex.members].split( + ' ', + )) { + if (this.members[importedMember]) { + const wekanId = this.members[importedMember]; + if (!wekanMembers.find(wId => wId === wekanId)) { + wekanMembers.push(wekanId); + } + } + } + if (wekanMembers.length > 0) { + cardToCreate.members = wekanMembers; + } + } + // add the custom fields + if (this.fieldIndex.customFields.length > 0) { + const customFields = []; + this.fieldIndex.customFields.forEach(customField => { + if (csvData[i][customField.position] !== ' ') { + if (customField.type === 'dropdown') { + customFields.push({ + _id: customField.id, + value: customField.settings.dropdownItems.find( + ({ name }) => name === csvData[i][customField.position], + )._id, + }); + } else { + customFields.push({ + _id: customField.id, + value: csvData[i][customField.position], + }); + } + } + cardToCreate.customFields = customFields; + }); + Cards.direct.insert(cardToCreate); + } + } + } + + create(board, currentBoardId) { + const isSandstorm = + Meteor.settings && + Meteor.settings.public && + Meteor.settings.public.sandstorm; + if (isSandstorm && currentBoardId) { + const currentBoard = Boards.findOne(currentBoardId); + currentBoard.archive(); + } + this.mapHeadertoCardFieldIndex(board[0]); + const boardId = this.createBoard(board); + this.createLists(board, boardId); + this.createSwimlanes(boardId); + this.createCustomFields(boardId); + this.createCards(board, boardId); + return boardId; + } +} diff --git a/models/customFields.js b/models/customFields.js index cc798b168..debd35c6a 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -22,7 +22,15 @@ CustomFields.attachSchema( * type of the custom field */ type: String, - allowedValues: ['text', 'number', 'date', 'dropdown'], + allowedValues: [ + 'text', + 'number', + 'date', + 'dropdown', + 'checkbox', + 'currency', + 'stringtemplate', + ], }, settings: { /** @@ -30,6 +38,10 @@ CustomFields.attachSchema( */ type: Object, }, + 'settings.currencyCode': { + type: String, + optional: true, + }, 'settings.dropdownItems': { /** * list of drop down items objects @@ -53,23 +65,41 @@ CustomFields.attachSchema( }, }), }, + 'settings.stringtemplateFormat': { + type: String, + optional: true, + }, + 'settings.stringtemplateSeparator': { + type: String, + optional: true, + }, showOnCard: { /** * should we show on the cards this custom field */ type: Boolean, + defaultValue: false, }, automaticallyOnCard: { /** * should the custom fields automatically be added on cards? */ type: Boolean, + defaultValue: false, + }, + alwaysOnCard: { + /** + * should the custom field be automatically added to all cards? + */ + type: Boolean, + defaultValue: false, }, showLabelOnMiniCard: { /** * should the label of the custom field be shown on minicards? */ type: Boolean, + defaultValue: false, }, createdAt: { type: Date, @@ -100,6 +130,19 @@ CustomFields.attachSchema( }), ); +CustomFields.addToAllCards = cf => { + Cards.update( + { + boardId: { $in: cf.boardIds }, + customFields: { $not: { $elemMatch: { _id: cf._id } } }, + }, + { + $push: { customFields: { _id: cf._id, value: null } }, + }, + { multi: true }, + ); +}; + CustomFields.mutations({ addBoard(boardId) { if (boardId) { @@ -168,16 +211,14 @@ function customFieldDeletion(userId, doc) { function customFieldEdit(userId, doc) { const card = Cards.findOne(doc.cardId); const customFieldValue = Activities.findOne({ customFieldId: doc._id }).value; - const boardId = card.boardId; - //boardId: doc.boardIds[0], // We are creating a customField, it has only one boardId Activities.insert({ userId, activityType: 'setCustomField', - boardId, + boardId: doc.boardIds[0], // We are creating a customField, it has only one boardId customFieldId: doc._id, customFieldValue, - listId: card.listId, - swimlaneId: card.swimlaneId, + listId: doc.listId, + swimlaneId: doc.swimlaneId, }); } @@ -189,6 +230,10 @@ if (Meteor.isServer) { CustomFields.after.insert((userId, doc) => { customFieldCreation(userId, doc); + + if (doc.alwaysOnCard) { + CustomFields.addToAllCards(doc); + } }); CustomFields.before.update((userId, doc, fieldNames, modifier) => { @@ -202,8 +247,8 @@ if (Meteor.isServer) { Activities.remove({ customFieldId: doc._id, boardId: modifier.$pull.boardIds, - listId: card.listId, - swimlaneId: card.swimlaneId, + listId: doc.listId, + swimlaneId: doc.swimlaneId, }); } else if (_.contains(fieldNames, 'boardIds') && modifier.$push) { Activities.insert({ @@ -215,6 +260,11 @@ if (Meteor.isServer) { } }); + CustomFields.after.update((userId, doc) => { + if (doc.alwaysOnCard) { + CustomFields.addToAllCards(doc); + } + }); CustomFields.before.remove((userId, doc) => { customFieldDeletion(userId, doc); Activities.remove({ @@ -244,8 +294,8 @@ if (Meteor.isServer) { req, res, ) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, data: CustomFields.find({ boardIds: { $in: [paramBoardId] } }).map( @@ -266,14 +316,15 @@ if (Meteor.isServer) { * * @param {string} boardID the ID of the board * @param {string} customFieldId the ID of the custom field - * @return_type CustomFields + * @return_type [{_id: string, + * boardIds: string}] */ JsonRoutes.add( 'GET', '/api/boards/:boardId/custom-fields/:customFieldId', function(req, res) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramCustomFieldId = req.params.customFieldId; JsonRoutes.sendResult(res, { code: 200, @@ -302,8 +353,8 @@ if (Meteor.isServer) { req, res, ) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const board = Boards.findOne({ _id: paramBoardId }); const id = CustomFields.direct.insert({ name: req.body.name, @@ -329,6 +380,196 @@ if (Meteor.isServer) { }); }); + /** + * @operation edit_custom_field + * @summary Update a Custom Field + * + * @param {string} name the name of the custom field + * @param {string} type the type of the custom field + * @param {string} settings the settings object of the custom field + * @param {boolean} showOnCard should we show the custom field on cards + * @param {boolean} automaticallyOnCard should the custom fields automatically be added on cards + * @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards + * @return_type {_id: string} + */ + JsonRoutes.add( + 'PUT', + '/api/boards/:boardId/custom-fields/:customFieldId', + (req, res) => { + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); + + const paramFieldId = req.params.customFieldId; + + if (req.body.hasOwnProperty('name')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { name: req.body.name } }, + ); + } + if (req.body.hasOwnProperty('type')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { type: req.body.type } }, + ); + } + if (req.body.hasOwnProperty('settings')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { settings: req.body.settings } }, + ); + } + if (req.body.hasOwnProperty('showOnCard')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { showOnCard: req.body.showOnCard } }, + ); + } + if (req.body.hasOwnProperty('automaticallyOnCard')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { automaticallyOnCard: req.body.automaticallyOnCard } }, + ); + } + if (req.body.hasOwnProperty('alwaysOnCard')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { alwaysOnCard: req.body.alwaysOnCard } }, + ); + } + if (req.body.hasOwnProperty('showLabelOnMiniCard')) { + CustomFields.direct.update( + { _id: paramFieldId }, + { $set: { showLabelOnMiniCard: req.body.showLabelOnMiniCard } }, + ); + } + + JsonRoutes.sendResult(res, { + code: 200, + data: { _id: paramFieldId }, + }); + }, + ); + + /** + * @operation add_custom_field_dropdown_items + * @summary Update a Custom Field's dropdown items + * + * @param {string} [items] names of the custom field + * @return_type {_id: string} + */ + JsonRoutes.add( + 'POST', + '/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items', + (req, res) => { + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); + + const paramCustomFieldId = req.params.customFieldId; + const paramItems = req.body.items; + + if (req.body.hasOwnProperty('items')) { + if (Array.isArray(paramItems)) { + CustomFields.direct.update( + { _id: paramCustomFieldId }, + { + $push: { + 'settings.dropdownItems': { + $each: paramItems + .filter(name => typeof name === 'string') + .map(name => ({ + _id: Random.id(6), + name, + })), + }, + }, + }, + ); + } + } + + JsonRoutes.sendResult(res, { + code: 200, + data: { _id: paramCustomFieldId }, + }); + }, + ); + + /** + * @operation edit_custom_field_dropdown_item + * @summary Update a Custom Field's dropdown item + * + * @param {string} name names of the custom field + * @return_type {_id: string} + */ + JsonRoutes.add( + 'PUT', + '/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items/:dropdownItemId', + (req, res) => { + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); + + const paramDropdownItemId = req.params.dropdownItemId; + const paramCustomFieldId = req.params.customFieldId; + const paramName = req.body.name; + + if (req.body.hasOwnProperty('name')) { + CustomFields.direct.update( + { + _id: paramCustomFieldId, + 'settings.dropdownItems._id': paramDropdownItemId, + }, + { + $set: { + 'settings.dropdownItems.$': { + _id: paramDropdownItemId, + name: paramName, + }, + }, + }, + ); + } + + JsonRoutes.sendResult(res, { + code: 200, + data: { _id: customFieldId }, + }); + }, + ); + + /** + * @operation delete_custom_field_dropdown_item + * @summary Update a Custom Field's dropdown items + * + * @param {string} itemId ID of the dropdown item + * @return_type {_id: string} + */ + JsonRoutes.add( + 'DELETE', + '/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items/:dropdownItemId', + (req, res) => { + const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); + + paramCustomFieldId = req.params.customFieldId; + paramDropdownItemId = req.params.dropdownItemId; + + CustomFields.direct.update( + { _id: paramCustomFieldId }, + { + $pull: { + 'settings.dropdownItems': { _id: paramDropdownItemId }, + }, + }, + ); + + JsonRoutes.sendResult(res, { + code: 200, + data: { _id: paramCustomFieldId }, + }); + }, + ); + /** * @operation delete_custom_field * @summary Delete a Custom Fields attached to a board @@ -343,8 +584,8 @@ if (Meteor.isServer) { 'DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function(req, res) { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const id = req.params.customFieldId; CustomFields.remove({ _id: id, boardIds: { $in: [paramBoardId] } }); JsonRoutes.sendResult(res, { diff --git a/models/export.js b/models/export.js index 339123c8d..da8e3a491 100644 --- a/models/export.js +++ b/models/export.js @@ -1,3 +1,4 @@ +import { Exporter } from './exporter'; /* global JsonRoutes */ if (Meteor.isServer) { // todo XXX once we have a real API in place, move that route there @@ -7,10 +8,10 @@ if (Meteor.isServer) { // on the client instead of copy/pasting the route path manually between the // client and the server. /** - * @operation export + * @operation exportJson * @tag Boards * - * @summary This route is used to export the board. + * @summary This route is used to export the board to a json file format. * * @description If user is already logged-in, pass loginToken as param * "authToken": '/api/boards/:boardId/export?authToken=:token' @@ -21,21 +22,35 @@ if (Meteor.isServer) { * @param {string} boardId the ID of the board we are exporting * @param {string} authToken the loginToken */ - JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { + JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) { const boardId = req.params.boardId; let user = null; + let impersonateDone = false; + let adminId = null; const loginToken = req.query.authToken; if (loginToken) { const hashToken = Accounts._hashLoginToken(loginToken); user = Meteor.users.findOne({ 'services.resume.loginTokens.hashedToken': hashToken, }); + adminId = user._id.toString(); + impersonateDone = ImpersonatedUsers.findOne({ + adminId: adminId, + }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); user = Users.findOne({ _id: req.userId, isAdmin: true }); } const exporter = new Exporter(boardId); - if (exporter.canExport(user)) { + if (exporter.canExport(user) || impersonateDone) { + if (impersonateDone) { + ImpersonatedUsers.insert({ + adminId: adminId, + boardId: boardId, + reason: 'exportJSON', + }); + } + JsonRoutes.sendResult(res, { code: 200, data: exporter.build(), @@ -46,199 +61,135 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, 403); } }); -} -// exporter maybe is broken since Gridfs introduced, add fs and path - -export class Exporter { - constructor(boardId) { - this._boardId = boardId; - } - - build() { - const fs = Npm.require('fs'); - const os = Npm.require('os'); - const path = Npm.require('path'); - - const byBoard = { boardId: this._boardId }; - const byBoardNoLinked = { - boardId: this._boardId, - linkedId: { $in: ['', null] }, - }; - // we do not want to retrieve boardId in related elements - const noBoardId = { - fields: { - boardId: 0, - }, - }; - const result = { - _format: 'wekan-board-1.0.0', - }; - _.extend( - result, - Boards.findOne(this._boardId, { - fields: { - stars: 0, - }, - }), - ); - result.lists = Lists.find(byBoard, noBoardId).fetch(); - result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); - result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); - result.customFields = CustomFields.find( - { boardIds: { $in: [this.boardId] } }, - { fields: { boardId: 0 } }, - ).fetch(); - result.comments = CardComments.find(byBoard, noBoardId).fetch(); - result.activities = Activities.find(byBoard, noBoardId).fetch(); - result.rules = Rules.find(byBoard, noBoardId).fetch(); - result.checklists = []; - result.checklistItems = []; - result.subtaskItems = []; - result.triggers = []; - result.actions = []; - result.cards.forEach(card => { - result.checklists.push( - ...Checklists.find({ - cardId: card._id, - }).fetch(), - ); - result.checklistItems.push( - ...ChecklistItems.find({ - cardId: card._id, - }).fetch(), - ); - result.subtaskItems.push( - ...Cards.find({ - parentId: card._id, - }).fetch(), - ); - }); - result.rules.forEach(rule => { - result.triggers.push( - ...Triggers.find( - { - _id: rule.triggerId, - }, - noBoardId, - ).fetch(), - ); - result.actions.push( - ...Actions.find( - { - _id: rule.actionId, - }, - noBoardId, - ).fetch(), - ); - }); - - // [Old] for attachments we only export IDs and absolute url to original doc - // [New] Encode attachment to base64 - - const getBase64Data = function(doc, callback) { - let buffer = Buffer.allocUnsafe(0); - buffer.fill(0); - - // callback has the form function (err, res) {} - const tmpFile = path.join( - os.tmpdir(), - `tmpexport${process.pid}${Math.random()}`, - ); - const tmpWriteable = fs.createWriteStream(tmpFile); - const readStream = doc.createReadStream(); - readStream.on('data', function(chunk) { - buffer = Buffer.concat([buffer, chunk]); - }); - - readStream.on('error', function(err) { - callback(null, null); - }); - readStream.on('end', function() { - // done - fs.unlink(tmpFile, () => { - //ignored + // todo XXX once we have a real API in place, move that route there + // todo XXX also share the route definition between the client and the server + // so that we could use something like + // `ApiRoutes.path('boards/export', boardId)`` + // on the client instead of copy/pasting the route path manually between the + // client and the server. + /** + * @operation exportJson + * @tag Boards + * + * @summary This route is used to export a attachement to a json file format. + * + * @description If user is already logged-in, pass loginToken as param + * "authToken": '/api/boards/:boardId/attachments/:attachmentId/export?authToken=:token' + * + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} attachmentId the ID of the attachment we are exporting + * @param {string} authToken the loginToken + */ + JsonRoutes.add( + 'get', + '/api/boards/:boardId/attachments/:attachmentId/export', + function (req, res) { + const boardId = req.params.boardId; + const attachmentId = req.params.attachmentId; + let user = null; + let impersonateDone = false; + let adminId = null; + const loginToken = req.query.authToken; + if (loginToken) { + const hashToken = Accounts._hashLoginToken(loginToken); + user = Meteor.users.findOne({ + 'services.resume.loginTokens.hashedToken': hashToken, }); + adminId = user._id.toString(); + impersonateDone = ImpersonatedUsers.findOne({ + adminId: adminId, + }); + } else if (!Meteor.settings.public.sandstorm) { + Authentication.checkUserId(req.userId); + user = Users.findOne({ _id: req.userId, isAdmin: true }); + } + const exporter = new Exporter(boardId, attachmentId); + if (exporter.canExport(user) || impersonateDone) { + if (impersonateDone) { + ImpersonatedUsers.insert({ + adminId: adminId, + boardId: boardId, + attachmentId: attachmentId, + reason: 'exportJSONattachment', + }); + } + JsonRoutes.sendResult(res, { + code: 200, + data: exporter.build(), + }); + } else { + // we could send an explicit error message, but on the other hand the only + // way to get there is by hacking the UI so let's keep it raw. + JsonRoutes.sendResult(res, 403); + } + }, + ); - callback(null, buffer.toString('base64')); + /** + * @operation exportCSV/TSV + * @tag Boards + * + * @summary This route is used to export the board to a CSV or TSV file format. + * + * @description If user is already logged-in, pass loginToken as param + * + * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ + * for detailed explanations + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} authToken the loginToken + * @param {string} delimiter delimiter to use while building export. Default is comma ',' + */ + Picker.route('/api/boards/:boardId/export/csv', function (params, req, res) { + const boardId = params.boardId; + let user = null; + let impersonateDone = false; + let adminId = null; + const loginToken = params.query.authToken; + if (loginToken) { + const hashToken = Accounts._hashLoginToken(loginToken); + user = Meteor.users.findOne({ + 'services.resume.loginTokens.hashedToken': hashToken, }); - readStream.pipe(tmpWriteable); - }; - const getBase64DataSync = Meteor.wrapAsync(getBase64Data); - result.attachments = Attachments.find(byBoard) - .fetch() - .map(attachment => { - let filebase64 = null; - filebase64 = getBase64DataSync(attachment); - - return { - _id: attachment._id, - cardId: attachment.cardId, - //url: FlowRouter.url(attachment.url()), - file: filebase64, - name: attachment.original.name, - type: attachment.original.type, - }; + adminId = user._id.toString(); + impersonateDone = ImpersonatedUsers.findOne({ + adminId: adminId, }); - - // we also have to export some user data - as the other elements only - // include id but we have to be careful: - // 1- only exports users that are linked somehow to that board - // 2- do not export any sensitive information - const users = {}; - result.members.forEach(member => { - users[member.userId] = true; - }); - result.lists.forEach(list => { - users[list.userId] = true; - }); - result.cards.forEach(card => { - users[card.userId] = true; - if (card.members) { - card.members.forEach(memberId => { - users[memberId] = true; + } else if (!Meteor.settings.public.sandstorm) { + Authentication.checkUserId(req.userId); + user = Users.findOne({ + _id: req.userId, + isAdmin: true, + }); + } + const exporter = new Exporter(boardId); + if (exporter.canExport(user) || impersonateDone) { + if (impersonateDone) { + // TODO: Checking for CSV or TSV export type does not work: + // let exportType = 'export' + params.query.delimiter ? 'CSV' : 'TSV'; + // So logging export to CSV: + let exportType = 'exportCSV'; + ImpersonatedUsers.insert({ + adminId: adminId, + boardId: boardId, + reason: exportType, }); } - }); - result.comments.forEach(comment => { - users[comment.userId] = true; - }); - result.activities.forEach(activity => { - users[activity.userId] = true; - }); - result.checklists.forEach(checklist => { - users[checklist.userId] = true; - }); - const byUserIds = { - _id: { - $in: Object.getOwnPropertyNames(users), - }, - }; - // we use whitelist to be sure we do not expose inadvertently - // some secret fields that gets added to User later. - const userFields = { - fields: { - _id: 1, - username: 1, - 'profile.fullname': 1, - 'profile.initials': 1, - 'profile.avatarUrl': 1, - }, - }; - result.users = Users.find(byUserIds, userFields) - .fetch() - .map(user => { - // user avatar is stored as a relative url, we export absolute - if ((user.profile || {}).avatarUrl) { - user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); - } - return user; - }); - return result; - } - canExport(user) { - const board = Boards.findOne(this._boardId); - return board && board.isVisibleBy(user); - } + body = params.query.delimiter + ? exporter.buildCsv(params.query.delimiter) + : exporter.buildCsv(); + //'Content-Length': body.length, + res.writeHead(200, { + 'Content-Type': params.query.delimiter ? 'text/csv' : 'text/tsv', + }); + res.write(body); + res.end(); + } else { + res.writeHead(403); + res.end('Permission Error'); + } + }); } diff --git a/models/exportExcel.js b/models/exportExcel.js new file mode 100644 index 000000000..6ba7effdf --- /dev/null +++ b/models/exportExcel.js @@ -0,0 +1,67 @@ +import { runOnServer } from './runOnServer'; + +runOnServer(function() { + // the ExporterExcel class is only available on server and in order to import + // it here we use runOnServer to have it inside a function instead of an + // if (Meteor.isServer) block + import { ExporterExcel } from './server/ExporterExcel'; + + // todo XXX once we have a real API in place, move that route there + // todo XXX also share the route definition between the client and the server + // so that we could use something like + // `ApiRoutes.path('boards/exportExcel', boardId)`` + // on the client instead of copy/pasting the route path manually between the + // client and the server. + /** + * @operation exportExcel + * @tag Boards + * + * @summary This route is used to export the board Excel. + * + * @description If user is already logged-in, pass loginToken as param + * "authToken": '/api/boards/:boardId/exportExcel?authToken=:token' + * + * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ + * for detailed explanations + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} authToken the loginToken + */ + Picker.route('/api/boards/:boardId/exportExcel', function (params, req, res) { + const boardId = params.boardId; + let user = null; + let impersonateDone = false; + let adminId = null; + const loginToken = params.query.authToken; + if (loginToken) { + const hashToken = Accounts._hashLoginToken(loginToken); + user = Meteor.users.findOne({ + 'services.resume.loginTokens.hashedToken': hashToken, + }); + adminId = user._id.toString(); + impersonateDone = ImpersonatedUsers.findOne({ + adminId: adminId, + }); + } else if (!Meteor.settings.public.sandstorm) { + Authentication.checkUserId(req.userId); + user = Users.findOne({ + _id: req.userId, + isAdmin: true, + }); + } + + const exporterExcel = new ExporterExcel(boardId); + if (exporterExcel.canExport(user) || impersonateDone) { + if (impersonateDone) { + ImpersonatedUsers.insert({ + adminId: adminId, + boardId: boardId, + reason: 'exportExcel', + }); + } + exporterExcel.build(res); + } else { + res.end(TAPi18n.__('user-can-not-export-excel')); + } + }); +}); diff --git a/models/exportPDF.js b/models/exportPDF.js new file mode 100644 index 000000000..e698d2f52 --- /dev/null +++ b/models/exportPDF.js @@ -0,0 +1,70 @@ +import { runOnServer } from './runOnServer'; + +runOnServer(function() { + // the ExporterCardPDF class is only available on server and in order to import + // it here we use runOnServer to have it inside a function instead of an + // if (Meteor.isServer) block + import { ExporterCardPDF } from './server/ExporterCardPDF'; + + // todo XXX once we have a real API in place, move that route there + // todo XXX also share the route definition between the client and the server + // so that we could use something like + // `ApiRoutes.path('boards/exportExcel', boardId)`` + // on the client instead of copy/pasting the route path manually between the + // client and the server. + /** + * @operation exportExcel + * @tag Boards + * + * @summary This route is used to export the board Excel. + * + * @description If user is already logged-in, pass loginToken as param + * "authToken": '/api/boards/:boardId/exportExcel?authToken=:token' + * + * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ + * for detailed explanations + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} authToken the loginToken + */ + Picker.route('/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', function (params, req, res) { + const boardId = params.boardId; + const paramListId = req.params.listId; + const paramCardId = req.params.cardId; + let user = null; + let impersonateDone = false; + let adminId = null; + const loginToken = params.query.authToken; + if (loginToken) { + const hashToken = Accounts._hashLoginToken(loginToken); + user = Meteor.users.findOne({ + 'services.resume.loginTokens.hashedToken': hashToken, + }); + adminId = user._id.toString(); + impersonateDone = ImpersonatedUsers.findOne({ + adminId: adminId, + }); + } else if (!Meteor.settings.public.sandstorm) { + Authentication.checkUserId(req.userId); + user = Users.findOne({ + _id: req.userId, + isAdmin: true, + }); + } + + const exporterCardPDF = new ExporterCardPDF(boardId); + if (exporterCardPDF.canExport(user) || impersonateDone) { + if (impersonateDone) { + ImpersonatedUsers.insert({ + adminId: adminId, + boardId: boardId, + reason: 'exportCardPDF', + }); + } + + exporterCardPDF.build(res); + } else { + res.end(TAPi18n.__('user-can-not-export-card-to-pdf')); + } + }); +}); diff --git a/models/exporter.js b/models/exporter.js new file mode 100644 index 000000000..3a671fce4 --- /dev/null +++ b/models/exporter.js @@ -0,0 +1,422 @@ +const Papa = require('papaparse'); + +// exporter maybe is broken since Gridfs introduced, add fs and path +export class Exporter { + constructor(boardId, attachmentId) { + this._boardId = boardId; + this._attachmentId = attachmentId; + } + + build() { + const fs = Npm.require('fs'); + const os = Npm.require('os'); + const path = Npm.require('path'); + + const byBoard = { boardId: this._boardId }; + const byBoardNoLinked = { + boardId: this._boardId, + linkedId: { $in: ['', null] }, + }; + // we do not want to retrieve boardId in related elements + const noBoardId = { + fields: { + boardId: 0, + }, + }; + const result = { + _format: 'wekan-board-1.0.0', + }; + _.extend( + result, + Boards.findOne(this._boardId, { + fields: { + stars: 0, + }, + }), + ); + + // [Old] for attachments we only export IDs and absolute url to original doc + // [New] Encode attachment to base64 + + const getBase64Data = function (doc, callback) { + let buffer = Buffer.allocUnsafe(0); + buffer.fill(0); + + // callback has the form function (err, res) {} + const tmpFile = path.join( + os.tmpdir(), + `tmpexport${process.pid}${Math.random()}`, + ); + const tmpWriteable = fs.createWriteStream(tmpFile); + const readStream = doc.createReadStream(); + readStream.on('data', function (chunk) { + buffer = Buffer.concat([buffer, chunk]); + }); + + readStream.on('error', function () { + callback(null, null); + }); + readStream.on('end', function () { + // done + fs.unlink(tmpFile, () => { + //ignored + }); + + callback(null, buffer.toString('base64')); + }); + readStream.pipe(tmpWriteable); + }; + const getBase64DataSync = Meteor.wrapAsync(getBase64Data); + const byBoardAndAttachment = this._attachmentId + ? { boardId: this._boardId, _id: this._attachmentId } + : byBoard; + result.attachments = Attachments.find(byBoardAndAttachment) + .fetch() + .map((attachment) => { + let filebase64 = null; + filebase64 = getBase64DataSync(attachment); + + return { + _id: attachment._id, + cardId: attachment.cardId, + //url: FlowRouter.url(attachment.url()), + file: filebase64, + name: attachment.original.name, + type: attachment.original.type, + }; + }); + //When has a especific valid attachment return the single element + if (this._attachmentId) { + return result.attachments.length > 0 ? result.attachments[0] : {}; + } + + result.lists = Lists.find(byBoard, noBoardId).fetch(); + result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); + result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); + result.customFields = CustomFields.find( + { boardIds: this._boardId }, + { fields: { boardIds: 0 } }, + ).fetch(); + result.comments = CardComments.find(byBoard, noBoardId).fetch(); + result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); + result.checklists = []; + result.checklistItems = []; + result.subtaskItems = []; + result.triggers = []; + result.actions = []; + result.cards.forEach((card) => { + result.checklists.push( + ...Checklists.find({ + cardId: card._id, + }).fetch(), + ); + result.checklistItems.push( + ...ChecklistItems.find({ + cardId: card._id, + }).fetch(), + ); + result.subtaskItems.push( + ...Cards.find({ + parentId: card._id, + }).fetch(), + ); + }); + result.rules.forEach((rule) => { + result.triggers.push( + ...Triggers.find( + { + _id: rule.triggerId, + }, + noBoardId, + ).fetch(), + ); + result.actions.push( + ...Actions.find( + { + _id: rule.actionId, + }, + noBoardId, + ).fetch(), + ); + }); + + // we also have to export some user data - as the other elements only + // include id but we have to be careful: + // 1- only exports users that are linked somehow to that board + // 2- do not export any sensitive information + const users = {}; + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); + result.cards.forEach((card) => { + users[card.userId] = true; + if (card.members) { + card.members.forEach((memberId) => { + users[memberId] = true; + }); + } + }); + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users), + }, + }; + // we use whitelist to be sure we do not expose inadvertently + // some secret fields that gets added to User later. + const userFields = { + fields: { + _id: 1, + username: 1, + 'profile.fullname': 1, + 'profile.initials': 1, + 'profile.avatarUrl': 1, + }, + }; + result.users = Users.find(byUserIds, userFields) + .fetch() + .map((user) => { + // user avatar is stored as a relative url, we export absolute + if ((user.profile || {}).avatarUrl) { + user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); + } + return user; + }); + return result; + } + + buildCsv(delimiter = ',') { + const result = this.build(); + const columnHeaders = []; + const cardRows = []; + + const papaconfig = { + delimiter, // get parameter (was: auto-detect) + worker: true, + }; + + /* + newline: "", // auto-detect + quoteChar: '"', + escapeChar: '"', + header: true, + transformHeader: undefined, + dynamicTyping: false, + preview: 0, + encoding: "", + comments: false, + step: undefined, + complete: undefined, + error: undefined, + download: false, + downloadRequestHeaders: undefined, + downloadRequestBody: undefined, + skipEmptyLines: false, + chunk: undefined, + chunkSize: undefined, + fastMode: undefined, + beforeFirstChunk: undefined, + withCredentials: undefined, + transform: undefined + }; + */ + + //delimitersToGuess: [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP] + + columnHeaders.push( + 'Title', + 'Description', + 'Status', + 'Swimlane', + 'Owner', + 'Requested by', + 'Assigned by', + 'Members', + 'Assignees', + 'Labels', + 'Start at', + 'Due at', + 'End at', + 'Over time', + 'Spent time (hours)', + 'Created at', + 'Last modified at', + 'Last activity', + 'Vote', + 'Archived', + ); + const customFieldMap = {}; + let i = 0; + result.customFields.forEach((customField) => { + customFieldMap[customField._id] = { + position: i, + type: customField.type, + }; + if (customField.type === 'dropdown') { + let options = ''; + customField.settings.dropdownItems.forEach((item) => { + options = options === '' ? item.name : `${`${options}/${item.name}`}`; + }); + columnHeaders.push( + `CustomField-${customField.name}-${customField.type}-${options}`, + ); + } else if (customField.type === 'currency') { + columnHeaders.push( + `CustomField-${customField.name}-${customField.type}-${customField.settings.currencyCode}`, + ); + } else { + columnHeaders.push( + `CustomField-${customField.name}-${customField.type}`, + ); + } + i++; + }); + cardRows.push([[columnHeaders]]); + /* TODO: Try to get translations working. + These currently only bring English translations. + TAPi18n.__('title'), + TAPi18n.__('description'), + TAPi18n.__('status'), + TAPi18n.__('swimlane'), + TAPi18n.__('owner'), + TAPi18n.__('requested-by'), + TAPi18n.__('assigned-by'), + TAPi18n.__('members'), + TAPi18n.__('assignee'), + TAPi18n.__('labels'), + TAPi18n.__('card-start'), + TAPi18n.__('card-due'), + TAPi18n.__('card-end'), + TAPi18n.__('overtime-hours'), + TAPi18n.__('spent-time-hours'), + TAPi18n.__('createdAt'), + TAPi18n.__('last-modified-at'), + TAPi18n.__('last-activity'), + TAPi18n.__('voting'), + TAPi18n.__('archived'), + */ + + result.cards.forEach((card) => { + const currentRow = []; + currentRow.push(card.title); + currentRow.push(card.description); + currentRow.push( + result.lists.find(({ _id }) => _id === card.listId).title, + ); + currentRow.push( + result.swimlanes.find(({ _id }) => _id === card.swimlaneId).title, + ); + currentRow.push( + result.users.find(({ _id }) => _id === card.userId).username, + ); + currentRow.push(card.requestedBy ? card.requestedBy : ' '); + currentRow.push(card.assignedBy ? card.assignedBy : ' '); + let usernames = ''; + card.members.forEach((memberId) => { + const user = result.users.find(({ _id }) => _id === memberId); + usernames = `${usernames + user.username} `; + }); + currentRow.push(usernames.trim()); + let assignees = ''; + card.assignees.forEach((assigneeId) => { + const user = result.users.find(({ _id }) => _id === assigneeId); + assignees = `${assignees + user.username} `; + }); + currentRow.push(assignees.trim()); + let labels = ''; + card.labelIds.forEach((labelId) => { + const label = result.labels.find(({ _id }) => _id === labelId); + labels = `${labels + label.name}-${label.color} `; + }); + currentRow.push(labels.trim()); + currentRow.push(card.startAt ? moment(card.startAt).format() : ' '); + currentRow.push(card.dueAt ? moment(card.dueAt).format() : ' '); + currentRow.push(card.endAt ? moment(card.endAt).format() : ' '); + currentRow.push(card.isOvertime ? 'true' : 'false'); + currentRow.push(card.spentTime); + currentRow.push(card.createdAt ? moment(card.createdAt).format() : ' '); + currentRow.push(card.modifiedAt ? moment(card.modifiedAt).format() : ' '); + currentRow.push( + card.dateLastActivity ? moment(card.dateLastActivity).format() : ' ', + ); + if (card.vote && card.vote.question !== '') { + let positiveVoters = ''; + let negativeVoters = ''; + card.vote.positive.forEach((userId) => { + const user = result.users.find(({ _id }) => _id === userId); + positiveVoters = `${positiveVoters + user.username} `; + }); + card.vote.negative.forEach((userId) => { + const user = result.users.find(({ _id }) => _id === userId); + negativeVoters = `${negativeVoters + user.username} `; + }); + const votingResult = `${ + card.vote.public + ? `yes-${ + card.vote.positive.length + }-${positiveVoters.trimRight()}-no-${ + card.vote.negative.length + }-${negativeVoters.trimRight()}` + : `yes-${card.vote.positive.length}-no-${card.vote.negative.length}` + }`; + currentRow.push(`${card.vote.question}-${votingResult}`); + } else { + currentRow.push(' '); + } + currentRow.push(card.archived ? 'true' : 'false'); + //Custom fields + const customFieldValuesToPush = new Array(result.customFields.length); + card.customFields.forEach((field) => { + if (field.value !== null) { + if (customFieldMap[field._id].type === 'date') { + customFieldValuesToPush[customFieldMap[field._id].position] = + moment(field.value).format(); + } else if (customFieldMap[field._id].type === 'dropdown') { + const dropdownOptions = result.customFields.find( + ({ _id }) => _id === field._id, + ).settings.dropdownItems; + const fieldValue = dropdownOptions.find( + ({ _id }) => _id === field.value, + ).name; + customFieldValuesToPush[customFieldMap[field._id].position] = + fieldValue; + } else { + customFieldValuesToPush[customFieldMap[field._id].position] = + field.value; + } + } + }); + for ( + let valueIndex = 0; + valueIndex < customFieldValuesToPush.length; + valueIndex++ + ) { + if (!(valueIndex in customFieldValuesToPush)) { + currentRow.push(' '); + } else { + currentRow.push(customFieldValuesToPush[valueIndex]); + } + } + cardRows.push([[currentRow]]); + }); + + return Papa.unparse(cardRows, papaconfig); + } + + canExport(user) { + const board = Boards.findOne(this._boardId); + return board && board.isVisibleBy(user); + } +} diff --git a/models/impersonatedUsers.js b/models/impersonatedUsers.js new file mode 100644 index 000000000..97d15f10b --- /dev/null +++ b/models/impersonatedUsers.js @@ -0,0 +1,79 @@ +ImpersonatedUsers = new Mongo.Collection('impersonatedUsers'); + +/** + * A Impersonated User in wekan + */ +ImpersonatedUsers.attachSchema( + new SimpleSchema({ + adminId: { + /** + * the admin userid that impersonates + */ + type: String, + optional: true, + }, + userId: { + /** + * the userId that is impersonated + */ + type: String, + optional: true, + }, + boardId: { + /** + * the boardId that was exported by anyone that has sometime impersonated + */ + type: String, + optional: true, + }, + attachmentId: { + /** + * the attachmentId that was exported by anyone that has sometime impersonated + */ + type: String, + optional: true, + }, + reason: { + /** + * the reason why impersonated, like exportJSON + */ + type: String, + optional: true, + }, + createdAt: { + /** + * creation date of the impersonation + */ + type: Date, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert) { + return new Date(); + } else if (this.isUpsert) { + return { + $setOnInsert: new Date(), + }; + } else { + this.unset(); + } + }, + }, + modifiedAt: { + /** + * modified date of the impersonation + */ + type: Date, + denyUpdate: false, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert || this.isUpsert || this.isUpdate) { + return new Date(); + } else { + this.unset(); + } + }, + }, + }), +); + +export default ImpersonatedUsers; diff --git a/models/import.js b/models/import.js index fbfb14831..7c6a30e28 100644 --- a/models/import.js +++ b/models/import.js @@ -1,22 +1,28 @@ import { TrelloCreator } from './trelloCreator'; import { WekanCreator } from './wekanCreator'; -import { Exporter } from './export'; -import wekanMembersMapper from './wekanmapper'; +import { CsvCreator } from './csvCreator'; +import { Exporter } from './exporter'; +import { getMembersToMap } from './wekanmapper'; Meteor.methods({ importBoard(board, data, importSource, currentBoard) { - check(board, Object); check(data, Object); check(importSource, String); check(currentBoard, Match.Maybe(String)); let creator; switch (importSource) { case 'trello': + check(board, Object); creator = new TrelloCreator(data); break; case 'wekan': + check(board, Object); creator = new WekanCreator(data); break; + case 'csv': + check(board, Array); + creator = new CsvCreator(data); + break; } // 1. check all parameters are ok from a syntax point of view @@ -36,9 +42,23 @@ Meteor.methods({ check(currentBoardId, Match.Maybe(String)); const exporter = new Exporter(sourceBoardId); const data = exporter.build(); - const addData = {}; - addData.membersMapping = wekanMembersMapper.getMembersToMap(data); - const creator = new WekanCreator(addData); + const additionalData = {}; + + //get the members to map + const membersMapping = getMembersToMap(data); + + //now mirror the mapping done in finishImport in client/components/import/import.js: + if (membersMapping) { + const mappingById = {}; + membersMapping.forEach(member => { + if (member.wekanId) { + mappingById[member.id] = member.wekanId; + } + }); + additionalData.membersMapping = mappingById; + } + + const creator = new WekanCreator(additionalData); //data.title = `${data.title } - ${ TAPi18n.__('copy-tag')}`; data.title = `${data.title}`; return creator.create(data, currentBoardId); diff --git a/models/lists.js b/models/lists.js index b123ab4f7..4bd9839a3 100644 --- a/models/lists.js +++ b/models/lists.js @@ -1,3 +1,5 @@ +import { ALLOWED_COLORS } from '/config/const'; + Lists = new Mongo.Collection('lists'); /** @@ -32,6 +34,13 @@ Lists.attachSchema( } }, }, + archivedAt: { + /** + * latest archiving date + */ + type: Date, + optional: true, + }, boardId: { /** * the board associated to this list @@ -137,32 +146,7 @@ Lists.attachSchema( type: String, optional: true, // silver is the default, so it is left out - allowedValues: [ - 'white', - 'green', - 'yellow', - 'orange', - 'red', - 'purple', - 'blue', - 'sky', - 'lime', - 'pink', - 'black', - 'peachpuff', - 'crimson', - 'plum', - 'darkgreen', - 'slateblue', - 'magenta', - 'gold', - 'navy', - 'gray', - 'saddlebrown', - 'paleturquoise', - 'mistyrose', - 'indigo', - ], + allowedValues: ALLOWED_COLORS, }, type: { /** @@ -195,7 +179,7 @@ Lists.helpers({ this.swimlaneId = swimlaneId; let _id = null; - existingListWithSameName = Lists.findOne({ + const existingListWithSameName = Lists.findOne({ boardId, title: this.title, archived: false, @@ -218,6 +202,35 @@ Lists.helpers({ }); }, + move(boardId, swimlaneId) { + const boardList = Lists.findOne({ + boardId, + title: this.title, + archived: false, + }); + let listId; + if (boardList) { + listId = boardList._id; + this.cards().forEach(card => { + card.move(boardId, this._id, boardList._id); + }); + } else { + console.log('list.title:', this.title); + console.log('boardList:', boardList); + listId = Lists.insert({ + title: this.title, + boardId, + type: this.type, + archived: false, + wipLimit: this.wipLimit, + }); + } + + this.cards(swimlaneId).forEach(card => { + card.move(boardId, swimlaneId, listId); + }); + }, + cards(swimlaneId) { const selector = { listId: this._id, @@ -257,7 +270,7 @@ Lists.helpers({ }, colorClass() { - if (this.color) return this.color; + if (this.color) return `list-header-${this.color}`; return ''; }, @@ -273,6 +286,10 @@ Lists.helpers({ const card = Cards.findOne({ listId: this._id }); return card && card.absoluteUrl(); }, + originRelativeUrl() { + const card = Cards.findOne({ listId: this._id }); + return card && card.originRelativeUrl(); + }, remove() { Lists.remove({ _id: this._id }); }, @@ -292,7 +309,7 @@ Lists.mutations({ return card.archive(); }); } - return { $set: { archived: true } }; + return { $set: { archived: true, archivedAt: new Date() } }; }, restore() { @@ -328,6 +345,16 @@ Lists.mutations({ }, }); +Lists.archivedLists = () => { + return Lists.find({ archived: true }); +}; + +Lists.archivedListIds = () => { + return Lists.archivedLists().map(list => { + return list._id; + }); +}; + Meteor.methods({ applyWipLimit(listId, limit) { check(listId, String); @@ -352,6 +379,25 @@ Meteor.methods({ const list = Lists.findOne({ _id: listId }); list.toggleSoftLimit(!list.getWipLimit('soft')); }, + + myLists() { + // my lists + return _.uniq( + Lists.find( + { + boardId: { $in: Boards.userBoardIds(this.userId) }, + archived: false, + }, + { + fields: { title: 1 }, + }, + ) + .fetch() + .map(list => { + return list.title; + }), + ).sort(); + }, }); Lists.hookOptions.after.update = { fetchPrevious: false }; @@ -360,6 +406,7 @@ if (Meteor.isServer) { Meteor.startup(() => { Lists._collection._ensureIndex({ modifiedAt: -1 }); Lists._collection._ensureIndex({ boardId: 1 }); + Lists._collection._ensureIndex({ archivedAt: -1 }); }); Lists.after.insert((userId, doc) => { @@ -484,8 +531,8 @@ if (Meteor.isServer) { */ JsonRoutes.add('POST', '/api/boards/:boardId/lists', function(req, res) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const board = Boards.findOne(paramBoardId); const id = Lists.insert({ title: req.body.title, @@ -522,8 +569,8 @@ if (Meteor.isServer) { res, ) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const paramListId = req.params.listId; Lists.remove({ _id: paramListId, boardId: paramBoardId }); JsonRoutes.sendResult(res, { diff --git a/models/org.js b/models/org.js index a24d829db..8efa9b64b 100644 --- a/models/org.js +++ b/models/org.js @@ -1,99 +1,54 @@ Org = new Mongo.Collection('org'); /** - * A Organization in wekan + * A Organization in Wekan. A Enterprise in Trello. */ Org.attachSchema( new SimpleSchema({ - _id: { + orgDisplayName: { /** - * the organization id + * the name to display for the organization */ - type: Number, - optional: true, - // eslint-disable-next-line consistent-return - autoValue() { - if (this.isInsert && !this.isSet) { - return incrementCounter('counters', 'orgId', 1); - } - }, - }, - version: { - /** - * the version of the organization - */ - type: Number, + type: String, optional: true, }, - name: { + orgDesc: { /** - * name of the organization + * the description the organization */ type: String, optional: true, max: 190, }, - address1: { + orgShortName: { /** - * address1 of the organization + * short name of the organization */ type: String, optional: true, max: 255, }, - address2: { + orgWebsite: { /** - * address2 of the organization + * website of the organization */ type: String, optional: true, max: 255, }, - city: { + orgIsActive: { /** - * city of the organization + * status of the organization */ - type: String, + type: Boolean, optional: true, - max: 255, - }, - state: { - /** - * state of the organization - */ - type: String, - optional: true, - max: 255, - }, - zipCode: { - /** - * zipCode of the organization - */ - type: String, - optional: true, - max: 50, - }, - country: { - /** - * country of the organization - */ - type: String, - optional: true, - max: 255, - }, - billingEmail: { - /** - * billingEmail of the organization - */ - type: String, - optional: true, - max: 255, }, createdAt: { /** * creation date of the organization */ type: Date, + denyUpdate: false, // eslint-disable-next-line consistent-return autoValue() { if (this.isInsert) { @@ -120,10 +75,149 @@ Org.attachSchema( }), ); +if (Meteor.isServer) { + Org.allow({ + insert(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + update(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + remove(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + fetch: [], + }); + + + Meteor.methods({ + setCreateOrg( + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + ) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(orgDisplayName, String); + check(orgDesc, String); + check(orgShortName, String); + check(orgWebsite, String); + check(orgIsActive, Boolean); + + const nOrgNames = Org.find({ orgShortName }).count(); + if (nOrgNames > 0) { + throw new Meteor.Error('orgname-already-taken'); + } else { + Org.insert({ + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + }); + } + } + }, + + setOrgDisplayName(org, orgDisplayName) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(org, Object); + check(orgDisplayName, String); + Org.update(org, { + $set: { orgDisplayName: orgDisplayNameorgShortName }, + }); + } + }, + + setOrgDesc(org, orgDesc) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(org, Object); + check(orgDesc, String); + Org.update(org, { + $set: { orgDesc: orgDesc }, + }); + } + }, + + setOrgShortName(org, orgShortName) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(org, Object); + check(orgShortName, String); + Org.update(org, { + $set: { orgShortName: orgShortName }, + }); + } + }, + + setOrgIsActive(org, orgIsActive) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(org, Object); + check(orgIsActive, Boolean); + Org.update(org, { + $set: { orgIsActive: orgIsActive }, + }); + } + }, + + setOrgAllFields( + org, + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + ) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(org, Object); + check(orgDisplayName, String); + check(orgDesc, String); + check(orgShortName, String); + check(orgWebsite, String); + check(orgIsActive, Boolean); + Org.update(org, { + $set: { + orgDisplayName: orgDisplayName, + orgDesc: orgDesc, + orgShortName: orgShortName, + orgWebsite: orgWebsite, + orgIsActive: orgIsActive, + }, + }); + } + }, + }); +} + if (Meteor.isServer) { // Index for Organization name. Meteor.startup(() => { - Org._collection._ensureIndex({ name: -1 }); + // Org._collection._ensureIndex({ name: -1 }); + Org._collection._ensureIndex({ orgDisplayName: -1 }); }); } diff --git a/models/presences.js b/models/presences.js new file mode 100644 index 000000000..94c8fdabb --- /dev/null +++ b/models/presences.js @@ -0,0 +1,12 @@ +if (Meteor.isServer) { + Meteor.startup(() => { + // Date of 7 days ago + let lastWeek = new Date(); + lastWeek.setDate(lastWeek.getDate() - 7); + + presences.remove({ ttl: { $lte: lastWeek } }); + + // Create index for serverId that is queried often + presences._collection._ensureIndex({ serverId: -1 }); + }); +} diff --git a/models/rules.js b/models/rules.js index 2e6729ccf..d82bf9270 100644 --- a/models/rules.js +++ b/models/rules.js @@ -62,6 +62,15 @@ Rules.helpers({ getTrigger() { return Triggers.findOne({ _id: this.triggerId }); }, + board() { + return Boards.findOne({ _id: this.boardId }); + }, + trigger() { + return Triggers.findOne({ _id: this.triggerId }); + }, + action() { + return Actions.findOne({ _id: this.actionId }); + }, }); Rules.allow({ diff --git a/models/runOnServer.js b/models/runOnServer.js new file mode 100644 index 000000000..8723b03cd --- /dev/null +++ b/models/runOnServer.js @@ -0,0 +1,8 @@ +/** + * Executes a function only if we are on the server. Use in combination + * with package-sepcific loader functions to create a "nested" import that + * prevents leakage of server-dependencies to the client. + * @param fct {function} the function to be executed on the server + * @return {*} a return value from the function, if there is any + */ +export const runOnServer = fct => Meteor.isServer && fct(); diff --git a/models/server/ExporterCardPDF.js b/models/server/ExporterCardPDF.js new file mode 100644 index 000000000..b226ef805 --- /dev/null +++ b/models/server/ExporterCardPDF.js @@ -0,0 +1,629 @@ +// exporter maybe is broken since Gridfs introduced, add fs and path +import { createWorkbook } from './createWorkbook'; + +class ExporterCardPDF { + constructor(boardId) { + this._boardId = boardId; + } + + build(res) { + + /* + const fs = Npm.require('fs'); + const os = Npm.require('os'); + const path = Npm.require('path'); + + const byBoard = { + boardId: this._boardId, + }; + const byBoardNoLinked = { + boardId: this._boardId, + linkedId: { + $in: ['', null], + }, + }; + // we do not want to retrieve boardId in related elements + const noBoardId = { + fields: { + boardId: 0, + }, + }; + const result = { + _format: 'wekan-board-1.0.0', + }; + _.extend( + result, + Boards.findOne(this._boardId, { + fields: { + stars: 0, + }, + }), + ); + result.lists = Lists.find(byBoard, noBoardId).fetch(); + result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); + result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); + result.customFields = CustomFields.find( + { + boardIds: { + $in: [this.boardId], + }, + }, + { + fields: { + boardId: 0, + }, + }, + ).fetch(); + result.comments = CardComments.find(byBoard, noBoardId).fetch(); + result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); + result.checklists = []; + result.checklistItems = []; + result.subtaskItems = []; + result.triggers = []; + result.actions = []; + result.cards.forEach((card) => { + result.checklists.push( + ...Checklists.find({ + cardId: card._id, + }).fetch(), + ); + result.checklistItems.push( + ...ChecklistItems.find({ + cardId: card._id, + }).fetch(), + ); + result.subtaskItems.push( + ...Cards.find({ + parentId: card._id, + }).fetch(), + ); + }); + result.rules.forEach((rule) => { + result.triggers.push( + ...Triggers.find( + { + _id: rule.triggerId, + }, + noBoardId, + ).fetch(), + ); + result.actions.push( + ...Actions.find( + { + _id: rule.actionId, + }, + noBoardId, + ).fetch(), + ); + }); + + // we also have to export some user data - as the other elements only + // include id but we have to be careful: + // 1- only exports users that are linked somehow to that board + // 2- do not export any sensitive information + const users = {}; + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); + result.cards.forEach((card) => { + users[card.userId] = true; + if (card.members) { + card.members.forEach((memberId) => { + users[memberId] = true; + }); + } + if (card.assignees) { + card.assignees.forEach((memberId) => { + users[memberId] = true; + }); + } + }); + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users), + }, + }; + // we use whitelist to be sure we do not expose inadvertently + // some secret fields that gets added to User later. + const userFields = { + fields: { + _id: 1, + username: 1, + 'profile.initials': 1, + 'profile.avatarUrl': 1, + }, + }; + result.users = Users.find(byUserIds, userFields) + .fetch() + .map((user) => { + // user avatar is stored as a relative url, we export absolute + if ((user.profile || {}).avatarUrl) { + user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); + } + return user; + }); + + //init exceljs workbook + const workbook = createWorkbook(); + workbook.creator = TAPi18n.__('export-board'); + workbook.lastModifiedBy = TAPi18n.__('export-board'); + workbook.created = new Date(); + workbook.modified = new Date(); + workbook.lastPrinted = new Date(); + const filename = `${result.title}.xlsx`; + //init worksheet + const worksheet = workbook.addWorksheet(result.title, { + properties: { + tabColor: { + argb: 'FFC0000', + }, + }, + pageSetup: { + paperSize: 9, + orientation: 'landscape', + }, + }); + //get worksheet + const ws = workbook.getWorksheet(result.title); + ws.properties.defaultRowHeight = 20; + //init columns + //Excel font. Western: Arial. zh-CN: 宋体 + ws.columns = [ + { + key: 'a', + width: 14, + }, + { + key: 'b', + width: 40, + }, + { + key: 'c', + width: 60, + }, + { + key: 'd', + width: 40, + }, + { + key: 'e', + width: 20, + }, + { + key: 'f', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'g', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'h', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'i', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'j', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'k', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'l', + width: 20, + }, + { + key: 'm', + width: 20, + }, + { + key: 'n', + width: 20, + }, + { + key: 'o', + width: 20, + }, + { + key: 'p', + width: 20, + }, + { + key: 'q', + width: 20, + }, + { + key: 'r', + width: 20, + }, + ]; + + //add title line + ws.mergeCells('A1:H1'); + ws.getCell('A1').value = result.title; + ws.getCell('A1').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '20', + }, + }; + ws.getCell('A1').alignment = { + vertical: 'middle', + horizontal: 'center', + }; + ws.getRow(1).height = 40; + //get member and assignee info + let jmem = ''; + let jassig = ''; + const jmeml = {}; + const jassigl = {}; + for (const i in result.users) { + jmem = `${jmem + result.users[i].username},`; + jmeml[result.users[i]._id] = result.users[i].username; + } + jmem = jmem.substr(0, jmem.length - 1); + for (const ia in result.users) { + jassig = `${jassig + result.users[ia].username},`; + jassigl[result.users[ia]._id] = result.users[ia].username; + } + jassig = jassig.substr(0, jassig.length - 1); + //get kanban list info + const jlist = {}; + for (const klist in result.lists) { + jlist[result.lists[klist]._id] = result.lists[klist].title; + } + //get kanban swimlanes info + const jswimlane = {}; + for (const kswimlane in result.swimlanes) { + jswimlane[result.swimlanes[kswimlane]._id] = + result.swimlanes[kswimlane].title; + } + //get kanban label info + const jlabel = {}; + var isFirst = 1; + for (const klabel in result.labels) { + // console.log(klabel); + if (isFirst == 0) { + jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`; + } else { + isFirst = 0; + jlabel[result.labels[klabel]._id] = result.labels[klabel].name; + } + } + //add data +8 hours + function addTZhours(jdate) { + const curdate = new Date(jdate); + const checkCorrectDate = moment(curdate); + if (checkCorrectDate.isValid()) { + return curdate; + } else { + return ' '; + } + ////Do not add 8 hours to GMT. Use GMT instead. + ////Could not yet figure out how to get localtime. + //return new Date(curdate.setHours(curdate.getHours() + 8)); + //return curdate; + } + //add blank row + ws.addRow().values = ['', '', '', '', '', '']; + //add kanban info + ws.addRow().values = [ + TAPi18n.__('createdAt'), + addTZhours(result.createdAt), + TAPi18n.__('modifiedAt'), + addTZhours(result.modifiedAt), + TAPi18n.__('members'), + jmem, + ]; + ws.getRow(3).font = { + name: TAPi18n.__('excel-font'), + size: 10, + bold: true, + }; + ws.mergeCells('F3:R3'); + ws.getCell('B3').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + bold: true, + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }; + //cell center + function cellCenter(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'center', + wrapText: true, + }; + } + function cellLeft(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'left', + wrapText: true, + }; + } + cellCenter('A3'); + cellCenter('B3'); + cellCenter('C3'); + cellCenter('D3'); + cellCenter('E3'); + cellLeft('F3'); + ws.getRow(3).height = 20; + //all border + function allBorder(cellno) { + ws.getCell(cellno).border = { + top: { + style: 'thin', + }, + left: { + style: 'thin', + }, + bottom: { + style: 'thin', + }, + right: { + style: 'thin', + }, + }; + } + allBorder('A3'); + allBorder('B3'); + allBorder('C3'); + allBorder('D3'); + allBorder('E3'); + allBorder('F3'); + //add blank row + ws.addRow().values = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]; + //add card title + //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签']; + //this is where order in which the excel file generates + ws.addRow().values = [ + TAPi18n.__('number'), + TAPi18n.__('title'), + TAPi18n.__('description'), + TAPi18n.__('parent-card'), + TAPi18n.__('owner'), + TAPi18n.__('createdAt'), + TAPi18n.__('last-modified-at'), + TAPi18n.__('card-received'), + TAPi18n.__('card-start'), + TAPi18n.__('card-due'), + TAPi18n.__('card-end'), + TAPi18n.__('list'), + TAPi18n.__('swimlane'), + TAPi18n.__('assignee'), + TAPi18n.__('members'), + TAPi18n.__('labels'), + TAPi18n.__('overtime-hours'), + TAPi18n.__('spent-time-hours'), + ]; + ws.getRow(5).height = 20; + allBorder('A5'); + allBorder('B5'); + allBorder('C5'); + allBorder('D5'); + allBorder('E5'); + allBorder('F5'); + allBorder('G5'); + allBorder('H5'); + allBorder('I5'); + allBorder('J5'); + allBorder('K5'); + allBorder('L5'); + allBorder('M5'); + allBorder('N5'); + allBorder('O5'); + allBorder('P5'); + allBorder('Q5'); + allBorder('R5'); + cellCenter('A5'); + cellCenter('B5'); + cellCenter('C5'); + cellCenter('D5'); + cellCenter('E5'); + cellCenter('F5'); + cellCenter('G5'); + cellCenter('H5'); + cellCenter('I5'); + cellCenter('J5'); + cellCenter('K5'); + cellCenter('L5'); + cellCenter('M5'); + cellCenter('N5'); + cellCenter('O5'); + cellCenter('P5'); + cellCenter('Q5'); + cellCenter('R5'); + ws.getRow(5).font = { + name: TAPi18n.__('excel-font'), + size: 12, + bold: true, + }; + //add blank row + //add card info + for (const i in result.cards) { + const jcard = result.cards[i]; + //get member info + let jcmem = ''; + for (const j in jcard.members) { + jcmem += jmeml[jcard.members[j]]; + jcmem += ' '; + } + //get assignee info + let jcassig = ''; + for (const ja in jcard.assignees) { + jcassig += jassigl[jcard.assignees[ja]]; + jcassig += ' '; + } + //get card label info + let jclabel = ''; + for (const jl in jcard.labelIds) { + jclabel += jlabel[jcard.labelIds[jl]]; + jclabel += ' '; + } + //get parent name + if (jcard.parentId) { + const parentCard = result.cards.find( + (card) => card._id === jcard.parentId, + ); + jcard.parentCardTitle = parentCard ? parentCard.title : ''; + } + + //add card detail + const t = Number(i) + 1; + ws.addRow().values = [ + t.toString(), + jcard.title, + jcard.description, + jcard.parentCardTitle, + jmeml[jcard.userId], + addTZhours(jcard.createdAt), + addTZhours(jcard.dateLastActivity), + addTZhours(jcard.receivedAt), + addTZhours(jcard.startAt), + addTZhours(jcard.dueAt), + addTZhours(jcard.endAt), + jlist[jcard.listId], + jswimlane[jcard.swimlaneId], + jcassig, + jcmem, + jclabel, + jcard.isOvertime ? 'true' : 'false', + jcard.spentTime, + ]; + const y = Number(i) + 6; + //ws.getRow(y).height = 25; + allBorder(`A${y}`); + allBorder(`B${y}`); + allBorder(`C${y}`); + allBorder(`D${y}`); + allBorder(`E${y}`); + allBorder(`F${y}`); + allBorder(`G${y}`); + allBorder(`H${y}`); + allBorder(`I${y}`); + allBorder(`J${y}`); + allBorder(`K${y}`); + allBorder(`L${y}`); + allBorder(`M${y}`); + allBorder(`N${y}`); + allBorder(`O${y}`); + allBorder(`P${y}`); + allBorder(`Q${y}`); + allBorder(`R${y}`); + cellCenter(`A${y}`); + ws.getCell(`B${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`C${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`M${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`N${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`O${y}`).alignment = { + wrapText: true, + }; + } + workbook.xlsx.write(res).then(function () {}); + */ + + var doc = new PDFDocument({size: 'A4', margin: 50}); + doc.fontSize(12); + doc.text('Some test text', 10, 30, {align: 'center', width: 200}); + this.response.writeHead(200, { + 'Content-type': 'application/pdf', + 'Content-Disposition': "attachment; filename=test.pdf" + }); + this.response.end( doc.outputSync() ); + + } + + canExport(user) { + const board = Boards.findOne(this._boardId); + return board && board.isVisibleBy(user); + } +} + +export { ExporterCardPDF }; diff --git a/models/server/ExporterExcel.js b/models/server/ExporterExcel.js new file mode 100644 index 000000000..e4a4c54c6 --- /dev/null +++ b/models/server/ExporterExcel.js @@ -0,0 +1,617 @@ +import { createWorkbook } from './createWorkbook'; + +// exporter maybe is broken since Gridfs introduced, add fs and path + +class ExporterExcel { + constructor(boardId) { + this._boardId = boardId; + } + + build(res) { + const fs = Npm.require('fs'); + const os = Npm.require('os'); + const path = Npm.require('path'); + + const byBoard = { + boardId: this._boardId, + }; + const byBoardNoLinked = { + boardId: this._boardId, + linkedId: { + $in: ['', null], + }, + }; + // we do not want to retrieve boardId in related elements + const noBoardId = { + fields: { + boardId: 0, + }, + }; + const result = { + _format: 'wekan-board-1.0.0', + }; + _.extend( + result, + Boards.findOne(this._boardId, { + fields: { + stars: 0, + }, + }), + ); + result.lists = Lists.find(byBoard, noBoardId).fetch(); + result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); + result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); + result.customFields = CustomFields.find( + { + boardIds: { + $in: [this.boardId], + }, + }, + { + fields: { + boardId: 0, + }, + }, + ).fetch(); + result.comments = CardComments.find(byBoard, noBoardId).fetch(); + result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); + result.checklists = []; + result.checklistItems = []; + result.subtaskItems = []; + result.triggers = []; + result.actions = []; + result.cards.forEach((card) => { + result.checklists.push( + ...Checklists.find({ + cardId: card._id, + }).fetch(), + ); + result.checklistItems.push( + ...ChecklistItems.find({ + cardId: card._id, + }).fetch(), + ); + result.subtaskItems.push( + ...Cards.find({ + parentId: card._id, + }).fetch(), + ); + }); + result.rules.forEach((rule) => { + result.triggers.push( + ...Triggers.find( + { + _id: rule.triggerId, + }, + noBoardId, + ).fetch(), + ); + result.actions.push( + ...Actions.find( + { + _id: rule.actionId, + }, + noBoardId, + ).fetch(), + ); + }); + + // we also have to export some user data - as the other elements only + // include id but we have to be careful: + // 1- only exports users that are linked somehow to that board + // 2- do not export any sensitive information + const users = {}; + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); + result.cards.forEach((card) => { + users[card.userId] = true; + if (card.members) { + card.members.forEach((memberId) => { + users[memberId] = true; + }); + } + if (card.assignees) { + card.assignees.forEach((memberId) => { + users[memberId] = true; + }); + } + }); + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users), + }, + }; + // we use whitelist to be sure we do not expose inadvertently + // some secret fields that gets added to User later. + const userFields = { + fields: { + _id: 1, + username: 1, + 'profile.initials': 1, + 'profile.avatarUrl': 1, + }, + }; + result.users = Users.find(byUserIds, userFields) + .fetch() + .map((user) => { + // user avatar is stored as a relative url, we export absolute + if ((user.profile || {}).avatarUrl) { + user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); + } + return user; + }); + + //init exceljs workbook + const workbook = createWorkbook(); + workbook.creator = TAPi18n.__('export-board'); + workbook.lastModifiedBy = TAPi18n.__('export-board'); + workbook.created = new Date(); + workbook.modified = new Date(); + workbook.lastPrinted = new Date(); + const filename = `${result.title}.xlsx`; + //init worksheet + const worksheet = workbook.addWorksheet(result.title, { + properties: { + tabColor: { + argb: 'FFC0000', + }, + }, + pageSetup: { + paperSize: 9, + orientation: 'landscape', + }, + }); + //get worksheet + const ws = workbook.getWorksheet(result.title); + ws.properties.defaultRowHeight = 20; + //init columns + //Excel font. Western: Arial. zh-CN: 宋体 + ws.columns = [ + { + key: 'a', + width: 14, + }, + { + key: 'b', + width: 40, + }, + { + key: 'c', + width: 60, + }, + { + key: 'd', + width: 40, + }, + { + key: 'e', + width: 20, + }, + { + key: 'f', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'g', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'h', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'i', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'j', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'k', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'l', + width: 20, + }, + { + key: 'm', + width: 20, + }, + { + key: 'n', + width: 20, + }, + { + key: 'o', + width: 20, + }, + { + key: 'p', + width: 20, + }, + { + key: 'q', + width: 20, + }, + { + key: 'r', + width: 20, + }, + ]; + + //add title line + ws.mergeCells('A1:H1'); + ws.getCell('A1').value = result.title; + ws.getCell('A1').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '20', + }, + }; + ws.getCell('A1').alignment = { + vertical: 'middle', + horizontal: 'center', + }; + ws.getRow(1).height = 40; + //get member and assignee info + let jmem = ''; + let jassig = ''; + const jmeml = {}; + const jassigl = {}; + for (const i in result.users) { + jmem = `${jmem + result.users[i].username},`; + jmeml[result.users[i]._id] = result.users[i].username; + } + jmem = jmem.substr(0, jmem.length - 1); + for (const ia in result.users) { + jassig = `${jassig + result.users[ia].username},`; + jassigl[result.users[ia]._id] = result.users[ia].username; + } + jassig = jassig.substr(0, jassig.length - 1); + //get kanban list info + const jlist = {}; + for (const klist in result.lists) { + jlist[result.lists[klist]._id] = result.lists[klist].title; + } + //get kanban swimlanes info + const jswimlane = {}; + for (const kswimlane in result.swimlanes) { + jswimlane[result.swimlanes[kswimlane]._id] = + result.swimlanes[kswimlane].title; + } + //get kanban label info + const jlabel = {}; + var isFirst = 1; + for (const klabel in result.labels) { + // console.log(klabel); + if (isFirst == 0) { + jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`; + } else { + isFirst = 0; + jlabel[result.labels[klabel]._id] = result.labels[klabel].name; + } + } + //add data +8 hours + function addTZhours(jdate) { + const curdate = new Date(jdate); + const checkCorrectDate = moment(curdate); + if (checkCorrectDate.isValid()) { + return curdate; + } else { + return ' '; + } + ////Do not add 8 hours to GMT. Use GMT instead. + ////Could not yet figure out how to get localtime. + //return new Date(curdate.setHours(curdate.getHours() + 8)); + //return curdate; + } + //add blank row + ws.addRow().values = ['', '', '', '', '', '']; + //add kanban info + ws.addRow().values = [ + TAPi18n.__('createdAt'), + addTZhours(result.createdAt), + TAPi18n.__('modifiedAt'), + addTZhours(result.modifiedAt), + TAPi18n.__('members'), + jmem, + ]; + ws.getRow(3).font = { + name: TAPi18n.__('excel-font'), + size: 10, + bold: true, + }; + ws.mergeCells('F3:R3'); + ws.getCell('B3').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + bold: true, + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }; + //cell center + function cellCenter(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'center', + wrapText: true, + }; + } + function cellLeft(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'left', + wrapText: true, + }; + } + cellCenter('A3'); + cellCenter('B3'); + cellCenter('C3'); + cellCenter('D3'); + cellCenter('E3'); + cellLeft('F3'); + ws.getRow(3).height = 20; + //all border + function allBorder(cellno) { + ws.getCell(cellno).border = { + top: { + style: 'thin', + }, + left: { + style: 'thin', + }, + bottom: { + style: 'thin', + }, + right: { + style: 'thin', + }, + }; + } + allBorder('A3'); + allBorder('B3'); + allBorder('C3'); + allBorder('D3'); + allBorder('E3'); + allBorder('F3'); + //add blank row + ws.addRow().values = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]; + //add card title + //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签']; + //this is where order in which the excel file generates + ws.addRow().values = [ + TAPi18n.__('number'), + TAPi18n.__('title'), + TAPi18n.__('description'), + TAPi18n.__('parent-card'), + TAPi18n.__('owner'), + TAPi18n.__('createdAt'), + TAPi18n.__('last-modified-at'), + TAPi18n.__('card-received'), + TAPi18n.__('card-start'), + TAPi18n.__('card-due'), + TAPi18n.__('card-end'), + TAPi18n.__('list'), + TAPi18n.__('swimlane'), + TAPi18n.__('assignee'), + TAPi18n.__('members'), + TAPi18n.__('labels'), + TAPi18n.__('overtime-hours'), + TAPi18n.__('spent-time-hours'), + ]; + ws.getRow(5).height = 20; + allBorder('A5'); + allBorder('B5'); + allBorder('C5'); + allBorder('D5'); + allBorder('E5'); + allBorder('F5'); + allBorder('G5'); + allBorder('H5'); + allBorder('I5'); + allBorder('J5'); + allBorder('K5'); + allBorder('L5'); + allBorder('M5'); + allBorder('N5'); + allBorder('O5'); + allBorder('P5'); + allBorder('Q5'); + allBorder('R5'); + cellCenter('A5'); + cellCenter('B5'); + cellCenter('C5'); + cellCenter('D5'); + cellCenter('E5'); + cellCenter('F5'); + cellCenter('G5'); + cellCenter('H5'); + cellCenter('I5'); + cellCenter('J5'); + cellCenter('K5'); + cellCenter('L5'); + cellCenter('M5'); + cellCenter('N5'); + cellCenter('O5'); + cellCenter('P5'); + cellCenter('Q5'); + cellCenter('R5'); + ws.getRow(5).font = { + name: TAPi18n.__('excel-font'), + size: 12, + bold: true, + }; + //add blank row + //add card info + for (const i in result.cards) { + const jcard = result.cards[i]; + //get member info + let jcmem = ''; + for (const j in jcard.members) { + jcmem += jmeml[jcard.members[j]]; + jcmem += ' '; + } + //get assignee info + let jcassig = ''; + for (const ja in jcard.assignees) { + jcassig += jassigl[jcard.assignees[ja]]; + jcassig += ' '; + } + //get card label info + let jclabel = ''; + for (const jl in jcard.labelIds) { + jclabel += jlabel[jcard.labelIds[jl]]; + jclabel += ' '; + } + //get parent name + if (jcard.parentId) { + const parentCard = result.cards.find( + (card) => card._id === jcard.parentId, + ); + jcard.parentCardTitle = parentCard ? parentCard.title : ''; + } + + //add card detail + const t = Number(i) + 1; + ws.addRow().values = [ + t.toString(), + jcard.title, + jcard.description, + jcard.parentCardTitle, + jmeml[jcard.userId], + addTZhours(jcard.createdAt), + addTZhours(jcard.dateLastActivity), + addTZhours(jcard.receivedAt), + addTZhours(jcard.startAt), + addTZhours(jcard.dueAt), + addTZhours(jcard.endAt), + jlist[jcard.listId], + jswimlane[jcard.swimlaneId], + jcassig, + jcmem, + jclabel, + jcard.isOvertime ? 'true' : 'false', + jcard.spentTime, + ]; + const y = Number(i) + 6; + //ws.getRow(y).height = 25; + allBorder(`A${y}`); + allBorder(`B${y}`); + allBorder(`C${y}`); + allBorder(`D${y}`); + allBorder(`E${y}`); + allBorder(`F${y}`); + allBorder(`G${y}`); + allBorder(`H${y}`); + allBorder(`I${y}`); + allBorder(`J${y}`); + allBorder(`K${y}`); + allBorder(`L${y}`); + allBorder(`M${y}`); + allBorder(`N${y}`); + allBorder(`O${y}`); + allBorder(`P${y}`); + allBorder(`Q${y}`); + allBorder(`R${y}`); + cellCenter(`A${y}`); + ws.getCell(`B${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`C${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`M${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`N${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`O${y}`).alignment = { + wrapText: true, + }; + } + workbook.xlsx.write(res).then(function () {}); + } + + canExport(user) { + const board = Boards.findOne(this._boardId); + return board && board.isVisibleBy(user); + } +} + +export { ExporterExcel }; diff --git a/models/server/createWorkbook.js b/models/server/createWorkbook.js new file mode 100644 index 000000000..493f678cd --- /dev/null +++ b/models/server/createWorkbook.js @@ -0,0 +1,5 @@ +import Excel from 'exceljs'; + +export const createWorkbook = function() { + return new Excel.Workbook(); +}; diff --git a/models/settings.js b/models/settings.js index 63bcd7f3f..286ef29f9 100644 --- a/models/settings.js +++ b/models/settings.js @@ -1,3 +1,8 @@ +// Sandstorm context is detected using the METEOR_SETTINGS environment variable +// in the package definition. +const isSandstorm = + Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; + Settings = new Mongo.Collection('settings'); Settings.attachSchema( @@ -41,10 +46,42 @@ Settings.attachSchema( type: String, optional: false, }, + spinnerName: { + type: String, + optional: true, + }, hideLogo: { type: Boolean, optional: true, }, + customLoginLogoImageUrl: { + type: String, + optional: true, + }, + customLoginLogoLinkUrl: { + type: String, + optional: true, + }, + textBelowCustomLoginLogo: { + type: String, + optional: true, + }, + automaticLinkedUrlSchemes: { + type: String, + optional: true, + }, + customTopLeftCornerLogoImageUrl: { + type: String, + optional: true, + }, + customTopLeftCornerLogoLinkUrl: { + type: String, + optional: true, + }, + customTopLeftCornerLogoHeight: { + type: String, + optional: true, + }, createdAt: { type: Date, denyUpdate: true, @@ -120,29 +157,38 @@ if (Meteor.isServer) { }; Settings.insert(defaultSetting); } - const newSetting = Settings.findOne(); - if (!process.env.MAIL_URL && newSetting.mailUrl()) - process.env.MAIL_URL = newSetting.mailUrl(); - Accounts.emailTemplates.from = process.env.MAIL_FROM - ? process.env.MAIL_FROM - : newSetting.mailServer.from; - }); - Settings.after.update((userId, doc, fieldNames) => { - // assign new values to mail-from & MAIL_URL in environment - if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) { - const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://'; - if (!doc.mailServer.username && !doc.mailServer.password) { - process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${doc.mailServer.port}/`; - } else { - process.env.MAIL_URL = `${protocol}${ - doc.mailServer.username - }:${encodeURIComponent(doc.mailServer.password)}@${ - doc.mailServer.host - }:${doc.mailServer.port}/`; - } - Accounts.emailTemplates.from = doc.mailServer.from; + if (isSandstorm) { + // At Sandstorm, Admin Panel has SMTP settings + const newSetting = Settings.findOne(); + if (!process.env.MAIL_URL && newSetting.mailUrl()) + process.env.MAIL_URL = newSetting.mailUrl(); + Accounts.emailTemplates.from = process.env.MAIL_FROM + ? process.env.MAIL_FROM + : newSetting.mailServer.from; + } else { + // Not running on Sandstorm, so using environment variables + Accounts.emailTemplates.from = process.env.MAIL_FROM; } }); + if (isSandstorm) { + // At Sandstorm Wekan Admin Panel, save SMTP settings. + Settings.after.update((userId, doc, fieldNames) => { + // assign new values to mail-from & MAIL_URL in environment + if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) { + const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://'; + if (!doc.mailServer.username && !doc.mailServer.password) { + process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${doc.mailServer.port}/`; + } else { + process.env.MAIL_URL = `${protocol}${ + doc.mailServer.username + }:${encodeURIComponent(doc.mailServer.password)}@${ + doc.mailServer.host + }:${doc.mailServer.port}/`; + } + Accounts.emailTemplates.from = doc.mailServer.from; + } + }); + } function getRandomNum(min, max) { const range = max - min; @@ -187,15 +233,26 @@ if (Meteor.isServer) { } function isLdapEnabled() { - return process.env.LDAP_ENABLE === 'true'; + return ( + process.env.LDAP_ENABLE === 'true' || process.env.LDAP_ENABLE === true + ); } function isOauth2Enabled() { - return process.env.OAUTH2_ENABLED === 'true'; + return ( + process.env.OAUTH2_ENABLED === 'true' || + process.env.OAUTH2_ENABLED === true + ); } function isCasEnabled() { - return process.env.CAS_ENABLED === 'true'; + return ( + process.env.CAS_ENABLED === 'true' || process.env.CAS_ENABLED === true + ); + } + + function isApiEnabled() { + return process.env.WITH_API === 'true' || process.env.WITH_API === true; } Meteor.methods({ @@ -255,7 +312,7 @@ if (Meteor.isServer) { throw new Meteor.Error('invalid-user'); } const user = Meteor.user(); - if (!user.emails && !user.emails[0] && user.emails[0].address) { + if (!user.emails || !user.emails[0] || !user.emails[0].address) { throw new Meteor.Error('email-invalid'); } this.unblock(); @@ -314,6 +371,10 @@ if (Meteor.isServer) { return isCasEnabled(); }, + _isApiEnabled() { + return isApiEnabled(); + }, + // Gets all connection methods to use it in the Template getAuthenticationsEnabled() { return { @@ -326,6 +387,10 @@ if (Meteor.isServer) { getDefaultAuthenticationMethod() { return process.env.DEFAULT_AUTHENTICATION_METHOD; }, + + isPasswordLoginDisabled() { + return process.env.PASSWORD_LOGIN_ENABLED === 'false'; + }, }); } diff --git a/models/swimlanes.js b/models/swimlanes.js index aa7016f7e..095268a8c 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -1,3 +1,5 @@ +import { ALLOWED_COLORS } from '/config/const'; + Swimlanes = new Mongo.Collection('swimlanes'); /** @@ -23,6 +25,13 @@ Swimlanes.attachSchema( } }, }, + archivedAt: { + /** + * latest archiving date of the swimlane + */ + type: Date, + optional: true, + }, boardId: { /** * the ID of the board the swimlane is attached to @@ -61,32 +70,7 @@ Swimlanes.attachSchema( type: String, optional: true, // silver is the default, so it is left out - allowedValues: [ - 'white', - 'green', - 'yellow', - 'orange', - 'red', - 'purple', - 'blue', - 'sky', - 'lime', - 'pink', - 'black', - 'peachpuff', - 'crimson', - 'plum', - 'darkgreen', - 'slateblue', - 'magenta', - 'gold', - 'navy', - 'gray', - 'saddlebrown', - 'paleturquoise', - 'mistyrose', - 'indigo', - ], + allowedValues: ALLOWED_COLORS, }, updatedAt: { /** @@ -163,6 +147,45 @@ Swimlanes.helpers({ }); }, + move(toBoardId) { + this.lists().forEach(list => { + const toList = Lists.findOne({ + boardId: toBoardId, + title: list.title, + archived: false, + }); + + let toListId; + if (toList) { + toListId = toList._id; + } else { + toListId = Lists.insert({ + title: list.title, + boardId: toBoardId, + type: list.type, + archived: false, + wipLimit: list.wipLimit, + }); + } + + Cards.find({ + listId: list._id, + swimlaneId: this._id, + }).forEach(card => { + card.move(toBoardId, this._id, toListId); + }); + }); + + Swimlanes.update(this._id, { + $set: { + boardId: toBoardId, + }, + }); + + // make sure there is a default swimlane + this.board().getDefaultSwimline(); + }, + cards() { return Cards.find( Filter.mongoSelector({ @@ -216,7 +239,7 @@ Swimlanes.helpers({ }, colorClass() { - if (this.color) return this.color; + if (this.color) return `swimlane-${this.color}`; return ''; }, @@ -259,7 +282,7 @@ Swimlanes.mutations({ return list.archive(); }); } - return { $set: { archived: true } }; + return { $set: { archived: true, archivedAt: new Date() } }; }, restore() { @@ -283,6 +306,16 @@ Swimlanes.mutations({ }, }); +Swimlanes.archivedSwimlanes = () => { + return Swimlanes.find({ archived: true }); +}; + +Swimlanes.archivedSwimlaneIds = () => { + return Swimlanes.archivedSwimlanes().map(swim => { + return swim._id; + }); +}; + Swimlanes.hookOptions.after.update = { fetchPrevious: false }; if (Meteor.isServer) { @@ -421,8 +454,8 @@ if (Meteor.isServer) { */ JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function(req, res) { try { - Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; + Authentication.checkBoardAccess(req.userId, paramBoardId); const board = Boards.findOne(paramBoardId); const id = Swimlanes.insert({ title: req.body.title, diff --git a/models/team.js b/models/team.js new file mode 100644 index 000000000..39733dd39 --- /dev/null +++ b/models/team.js @@ -0,0 +1,221 @@ +Team = new Mongo.Collection('team'); + +/** + * A Team in Wekan. Organization in Trello. + */ +Team.attachSchema( + new SimpleSchema({ + teamDisplayName: { + /** + * the name to display for the team + */ + type: String, + optional: true, + }, + teamDesc: { + /** + * the description the team + */ + type: String, + optional: true, + max: 190, + }, + teamShortName: { + /** + * short name of the team + */ + type: String, + optional: true, + max: 255, + }, + teamWebsite: { + /** + * website of the team + */ + type: String, + optional: true, + max: 255, + }, + teamIsActive: { + /** + * status of the team + */ + type: Boolean, + optional: true, + }, + createdAt: { + /** + * creation date of the team + */ + type: Date, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert) { + return new Date(); + } else if (this.isUpsert) { + return { $setOnInsert: new Date() }; + } else { + this.unset(); + } + }, + }, + modifiedAt: { + type: Date, + denyUpdate: false, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert || this.isUpsert || this.isUpdate) { + return new Date(); + } else { + this.unset(); + } + }, + }, + }), +); + +if (Meteor.isServer) { + Team.allow({ + insert(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + update(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + remove(userId, doc) { + const user = Users.findOne({ + _id: userId, + }); + if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) + return true; + if (!user) { + return false; + } + return doc._id === userId; + }, + fetch: [], + }); + + Meteor.methods({ + setCreateTeam( + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + ) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(teamDisplayName, String); + check(teamDesc, String); + check(teamShortName, String); + check(teamWebsite, String); + check(teamIsActive, Boolean); + + const nTeamNames = Team.find({ teamShortName }).count(); + if (nTeamNames > 0) { + throw new Meteor.Error('teamname-already-taken'); + } else { + Team.insert({ + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + }); + } + } + }, + + setTeamDisplayName(team, teamDisplayName) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(team, Object); + check(teamDisplayName, String); + Team.update(team, { + $set: { teamDisplayName: teamDisplayName }, + }); + } + }, + + setTeamDesc(team, teamDesc) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(team, Object); + check(teamDesc, String); + Team.update(team, { + $set: { teamDesc: teamDesc }, + }); + } + }, + + setTeamShortName(team, teamShortName) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(team, Object); + check(teamShortName, String); + Team.update(team, { + $set: { teamShortName: teamShortName }, + }); + } + }, + + setTeamIsActive(team, teamIsActive) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(team, Object); + check(teamIsActive, Boolean); + Team.update(team, { + $set: { teamIsActive: teamIsActive }, + }); + } + }, + + setTeamAllFields( + team, + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + ) { + if (Meteor.user() && Meteor.user().isAdmin) { + check(team, Object); + check(teamDisplayName, String); + check(teamDesc, String); + check(teamShortName, String); + check(teamWebsite, String); + check(teamIsActive, Boolean); + Team.update(team, { + $set: { + teamDisplayName: teamDisplayName, + teamDesc: teamDesc, + teamShortName: teamShortName, + teamWebsite: teamWebsite, + teamIsActive: teamIsActive, + }, + }); + } + }, + }); +} + +if (Meteor.isServer) { + // Index for Team name. + Meteor.startup(() => { + Team._collection._ensureIndex({ teamDisplayName: -1 }); + }); +} + +export default Team; diff --git a/models/trelloCreator.js b/models/trelloCreator.js index cb1a6a67b..98179b81f 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -40,6 +40,8 @@ export class TrelloCreator { // maps a trelloCardId to an array of trelloAttachments this.attachments = {}; + + this.customFields = {}; } /** @@ -90,7 +92,7 @@ export class TrelloCreator { check( trelloBoard, Match.ObjectIncluding({ - closed: Boolean, + // closed: Boolean, // issue #3840, should import closed Trello boards name: String, prefs: Match.ObjectIncluding({ // XXX refine control by validating 'background' against a list of @@ -155,12 +157,18 @@ export class TrelloCreator { // You must call parseActions before calling this one. createBoardAndLabels(trelloBoard) { + let color = 'blue'; + if (this.getColor(trelloBoard.prefs.background) !== undefined) { + color = this.getColor(trelloBoard.prefs.background); + } + const boardToCreate = { archived: trelloBoard.closed, - color: this.getColor(trelloBoard.prefs.background), + color: color, // very old boards won't have a creation activity so no creation date createdAt: this._now(this.createdAt.board), labels: [], + customFields: [], members: [ { userId: Meteor.userId(), @@ -174,7 +182,7 @@ export class TrelloCreator { permission: this.getPermission(trelloBoard.prefs.permissionLevel), slug: getSlug(trelloBoard.name) || 'board', stars: 0, - title: trelloBoard.name, + title: Boards.uniqueTitle(trelloBoard.name), }; // now add other members if (trelloBoard.memberships) { @@ -205,17 +213,19 @@ export class TrelloCreator { } }); } - trelloBoard.labels.forEach(label => { - const labelToCreate = { - _id: Random.id(6), - color: label.color ? label.color : 'black', - name: label.name, - }; - // We need to remember them by Trello ID, as this is the only ref we have - // when importing cards. - this.labels[label.id] = labelToCreate._id; - boardToCreate.labels.push(labelToCreate); - }); + if (trelloBoard.labels) { + trelloBoard.labels.forEach(label => { + const labelToCreate = { + _id: Random.id(6), + color: label.color ? label.color : 'black', + name: label.name, + }; + // We need to remember them by Trello ID, as this is the only ref we have + // when importing cards. + this.labels[label.id] = labelToCreate._id; + boardToCreate.labels.push(labelToCreate); + }); + } const boardId = Boards.direct.insert(boardToCreate); Boards.direct.update(boardId, { $set: { modifiedAt: this._now() } }); // log activity @@ -232,6 +242,37 @@ export class TrelloCreator { // not the author from the original object. userId: this._user(), }); + if (trelloBoard.customFields) { + trelloBoard.customFields.forEach(field => { + const fieldToCreate = { + // trelloId: field.id, + name: field.name, + showOnCard: field.display.cardFront, + showLabelOnMiniCard: field.display.cardFront, + automaticallyOnCard: true, + alwaysOnCard: false, + type: field.type, + boardIds: [boardId], + settings: {}, + }; + + if (field.type === 'list') { + fieldToCreate.type = 'dropdown'; + fieldToCreate.settings = { + dropdownItems: field.options.map(opt => { + return { + _id: opt.id, + name: opt.value.text, + }; + }), + }; + } + + // We need to remember them by Trello ID, as this is the only ref we have + // when importing cards. + this.customFields[field.id] = CustomFields.direct.insert(fieldToCreate); + }); + } return boardId; } @@ -285,6 +326,51 @@ export class TrelloCreator { cardToCreate.members = wekanMembers; } } + // add vote + if (card.idMembersVoted) { + // Trello only know's positive votes + const positiveVotes = []; + card.idMembersVoted.forEach(trelloId => { + if (this.members[trelloId]) { + const wekanId = this.members[trelloId]; + // we may map multiple Trello members to the same wekan user + // in which case we risk adding the same user multiple times + if (!positiveVotes.find(wId => wId === wekanId)) { + positiveVotes.push(wekanId); + } + } + return true; + }); + if (positiveVotes.length > 0) { + cardToCreate.vote = { + question: cardToCreate.title, + public: true, + positive: positiveVotes, + }; + } + } + + if (card.customFieldItems) { + cardToCreate.customFields = []; + card.customFieldItems.forEach(item => { + const custom = { + _id: this.customFields[item.idCustomField], + }; + if (item.idValue) { + custom.value = item.idValue; + } else if (item.value.hasOwnProperty('checked')) { + custom.value = item.value.checked === 'true'; + } else if (item.value.hasOwnProperty('text')) { + custom.value = item.value.text; + } else if (item.value.hasOwnProperty('date')) { + custom.value = item.value.date; + } else if (item.value.hasOwnProperty('number')) { + custom.value = item.value.number; + } + cardToCreate.customFields.push(custom); + }); + } + // insert card const cardId = Cards.direct.insert(cardToCreate); // keep track of Trello id => Wekan id @@ -337,39 +423,62 @@ export class TrelloCreator { const attachments = this.attachments[card.id]; const trelloCoverId = card.idAttachmentCover; if (attachments) { + const links = []; attachments.forEach(att => { - const file = new FS.File(); - // Simulating file.attachData on the client generates multiple errors - // - HEAD returns null, which causes exception down the line - // - the template then tries to display the url to the attachment which causes other errors - // so we make it server only, and let UI catch up once it is done, forget about latency comp. - const self = this; - if (Meteor.isServer) { - file.attachData(att.url, function(error) { - file.boardId = boardId; - file.cardId = cardId; - file.userId = self._user(att.idMemberCreator); - // The field source will only be used to prevent adding - // attachments' related activities automatically - file.source = 'import'; - if (error) { - throw error; - } else { - const wekanAtt = Attachments.insert(file, () => { - // we do nothing - }); - self.attachmentIds[att.id] = wekanAtt._id; - // - if (trelloCoverId === att.id) { - Cards.direct.update(cardId, { - $set: { coverId: wekanAtt._id }, + // if the attachment `name` and `url` are the same, then the + // attachment is an attached link + if (att.name === att.url) { + links.push(att.url); + } else { + const file = new FS.File(); + // Simulating file.attachData on the client generates multiple errors + // - HEAD returns null, which causes exception down the line + // - the template then tries to display the url to the attachment which causes other errors + // so we make it server only, and let UI catch up once it is done, forget about latency comp. + const self = this; + if (Meteor.isServer) { + file.attachData(att.url, function(error) { + file.boardId = boardId; + file.cardId = cardId; + file.userId = self._user(att.idMemberCreator); + // The field source will only be used to prevent adding + // attachments' related activities automatically + file.source = 'import'; + if (error) { + throw error; + } else { + const wekanAtt = Attachments.insert(file, () => { + // we do nothing }); + self.attachmentIds[att.id] = wekanAtt._id; + // + if (trelloCoverId === att.id) { + Cards.direct.update(cardId, { + $set: { coverId: wekanAtt._id }, + }); + } } - } - }); + }); + } } // todo XXX set cover - if need be }); + + if (links.length) { + let desc = cardToCreate.description.trim(); + if (desc) { + desc += '\n\n'; + } + desc += `## ${TAPi18n.__('links-heading')}\n`; + links.forEach(link => { + desc += `* ${link}\n`; + }); + Cards.direct.update(cardId, { + $set: { + description: desc, + }, + }); + } } result.push(cardId); }); diff --git a/models/users.js b/models/users.js index 20581e652..ab9beef74 100644 --- a/models/users.js +++ b/models/users.js @@ -1,3 +1,6 @@ +import { SyncedCron } from 'meteor/percolate:synced-cron'; +import ImpersonatedUsers from './impersonatedUsers'; + // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. const isSandstorm = @@ -35,6 +38,44 @@ Users.attachSchema( } }, }, + orgs: { + /** + * the list of organizations that a user belongs to + */ + type: [Object], + optional: true, + }, + 'orgs.$.orgId':{ + /** + * The uniq ID of the organization + */ + type: String, + }, + 'orgs.$.orgDisplayName':{ + /** + * The display name of the organization + */ + type: String, + }, + teams: { + /** + * the list of teams that a user belongs to + */ + type: [Object], + optional: true, + }, + 'teams.$.teamId':{ + /** + * The uniq ID of the team + */ + type: String, + }, + 'teams.$.teamDisplayName':{ + /** + * The display name of the team + */ + type: String, + }, emails: { /** * the list of emails attached to a user @@ -65,7 +106,9 @@ Users.attachSchema( if (this.isInsert) { return new Date(); } else if (this.isUpsert) { - return { $setOnInsert: new Date() }; + return { + $setOnInsert: new Date(), + }; } else { this.unset(); } @@ -93,7 +136,7 @@ Users.attachSchema( autoValue() { if (this.isInsert && !this.isSet) { return { - boardView: 'board-view-lists', + boardView: 'board-view-swimlanes', }; } }, @@ -121,7 +164,21 @@ Users.attachSchema( }, 'profile.showDesktopDragHandles': { /** - * does the user want to hide system messages? + * does the user want to show desktop drag handles? + */ + type: Boolean, + optional: true, + }, + 'profile.hideCheckedItems': { + /** + * does the user want to hide checked checklist items? + */ + type: Boolean, + optional: true, + }, + 'profile.cardMaximized': { + /** + * has user clicked maximize card? */ type: Boolean, optional: true, @@ -188,6 +245,13 @@ Users.attachSchema( type: Number, optional: true, }, + 'profile.startDayOfWeek': { + /** + * startDayOfWeek field of the user + */ + type: Number, + optional: true, + }, 'profile.starredBoards': { /** * list of starred board IDs @@ -209,8 +273,8 @@ Users.attachSchema( type: String, optional: true, allowedValues: [ - 'board-view-lists', 'board-view-swimlanes', + 'board-view-lists', 'board-view-cal', ], }, @@ -295,12 +359,42 @@ Users.attachSchema( optional: false, defaultValue: 'password', }, + sessionData: { + /** + * profile settings + */ + type: Object, + optional: true, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert && !this.isSet) { + return {}; + } + }, + }, + 'sessionData.totalHits': { + /** + * Total hits from last searchquery['members.userId'] = Meteor.userId(); + * last hit that was returned + */ + type: Number, + optional: true, + }, + importUsernames: { + /** + * username for imported + */ + type: [String], + optional: true, + }, }), ); Users.allow({ update(userId, doc) { - const user = Users.findOne({ _id: userId }); + const user = Users.findOne({ + _id: userId, + }); if ((user && user.isAdmin) || (Meteor.user() && Meteor.user().isAdmin)) return true; if (!user) { @@ -309,10 +403,18 @@ Users.allow({ return doc._id === userId; }, remove(userId, doc) { - const adminsNumber = Users.find({ isAdmin: true }).count(); + const adminsNumber = Users.find({ + isAdmin: true, + }).count(); const { isAdmin } = Users.findOne( - { _id: userId }, - { fields: { isAdmin: 1 } }, + { + _id: userId, + }, + { + fields: { + isAdmin: 1, + }, + }, ); // Prevents remove of the only one administrator @@ -334,6 +436,16 @@ Users.initEasySearch(searchInFields, { returnFields: [...searchInFields, 'profile.avatarUrl'], }); +Users.safeFields = { + _id: 1, + username: 1, + 'profile.fullname': 1, + 'profile.avatarUrl': 1, + 'profile.initials': 1, + orgs: 1, + teams: 1, +}; + if (Meteor.isClient) { Users.helpers({ isBoardMember() { @@ -375,21 +487,76 @@ if (Meteor.isClient) { return board && board.hasWorker(this._id); }, - isBoardAdmin() { - const board = Boards.findOne(Session.get('currentBoard')); + isBoardAdmin(boardId = Session.get('currentBoard')) { + const board = Boards.findOne(boardId); return board && board.hasAdmin(this._id); }, }); } +Users.parseImportUsernames = (usernamesString) => { + return usernamesString.trim().split(new RegExp('\\s*[,;]\\s*')); +}; + Users.helpers({ + importUsernamesString() { + if (this.importUsernames) { + return this.importUsernames.join(', '); + } + return ''; + }, + orgsUserBelongs() { + if (this.orgs) { + return this.orgs.map(function(org){return org.orgDisplayName}).join(','); + } + return ''; + }, + orgIdsUserBelongs() { + if (this.orgs) { + return this.orgs.map(function(org){return org.orgId}).join(','); + } + return ''; + }, + teamsUserBelongs() { + if (this.teams) { + return this.teams.map(function(team){ return team.teamDisplayName}).join(','); + } + return ''; + }, + teamIdsUserBelongs() { + if (this.teams) { + return this.teams.map(function(team){ return team.teamId}).join(','); + } + return ''; + }, boards() { - return Boards.find({ 'members.userId': this._id }); + return Boards.find( + { + 'members.userId': this._id, + }, + { + sort: { + sort: 1 /* boards default sorting */, + }, + }, + ); }, starredBoards() { const { starredBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: starredBoards } }); + return Boards.find( + { + archived: false, + _id: { + $in: starredBoards, + }, + }, + { + sort: { + sort: 1 /* boards default sorting */, + }, + }, + ); }, hasStarred(boardId) { @@ -399,7 +566,19 @@ Users.helpers({ invitedBoards() { const { invitedBoards = [] } = this.profile || {}; - return Boards.find({ archived: false, _id: { $in: invitedBoards } }); + return Boards.find( + { + archived: false, + _id: { + $in: invitedBoards, + }, + }, + { + sort: { + sort: 1 /* boards default sorting */, + }, + }, + ); }, isInvitedTo(boardId) { @@ -461,11 +640,21 @@ Users.helpers({ return profile.showDesktopDragHandles || false; }, + hasHideCheckedItems() { + const profile = this.profile || {}; + return profile.hideCheckedItems || false; + }, + hasHiddenSystemMessages() { const profile = this.profile || {}; return profile.hiddenSystemMessages || false; }, + hasCardMaximized() { + const profile = this.profile || {}; + return profile.cardMaximized || false; + }, + hasHiddenMinicardLabelText() { const profile = this.profile || {}; return profile.hiddenMinicardLabelText || false; @@ -506,6 +695,15 @@ Users.helpers({ return profile.language || 'en'; }, + getStartDayOfWeek() { + const profile = this.profile || {}; + if (typeof profile.startDayOfWeek === 'undefined') { + // default is 'Monday' (1) + return 1; + } + return profile.startDayOfWeek; + }, + getTemplatesBoardId() { return (this.profile || {}).templatesBoardId; }, @@ -515,7 +713,9 @@ Users.helpers({ }, remove() { - User.remove({ _id: this._id }); + User.remove({ + _id: this._id, + }); }, }); @@ -573,6 +773,15 @@ Users.mutations({ }, }; }, + + setName(value) { + return { + $set: { + 'profile.fullname': value, + }, + }; + }, + toggleDesktopHandles(value = false) { return { $set: { @@ -581,6 +790,15 @@ Users.mutations({ }; }, + toggleHideCheckedItems() { + const value = this.hasHideCheckedItems(); + return { + $set: { + 'profile.hideCheckedItems': !value, + }, + }; + }, + toggleSystem(value = false) { return { $set: { @@ -589,6 +807,14 @@ Users.mutations({ }; }, + toggleCardMaximized(value = false) { + return { + $set: { + 'profile.cardMaximized': !value, + }, + }; + }, + toggleLabelText(value = false) { return { $set: { @@ -600,7 +826,9 @@ Users.mutations({ addNotification(activityId) { return { $addToSet: { - 'profile.notifications': { activity: activityId }, + 'profile.notifications': { + activity: activityId, + }, }, }; }, @@ -608,7 +836,9 @@ Users.mutations({ removeNotification(activityId) { return { $pull: { - 'profile.notifications': { activity: activityId }, + 'profile.notifications': { + activity: activityId, + }, }, }; }, @@ -630,11 +860,27 @@ Users.mutations({ }, setAvatarUrl(avatarUrl) { - return { $set: { 'profile.avatarUrl': avatarUrl } }; + return { + $set: { + 'profile.avatarUrl': avatarUrl, + }, + }; }, setShowCardsCountAt(limit) { - return { $set: { 'profile.showCardsCountAt': limit } }; + return { + $set: { + 'profile.showCardsCountAt': limit, + }, + }; + }, + + setStartDayOfWeek(startDay) { + return { + $set: { + 'profile.startDayOfWeek': startDay, + }, + }; }, setBoardView(view) { @@ -655,10 +901,18 @@ Meteor.methods({ const user = Meteor.user(); user.toggleDesktopHandles(user.hasShowDesktopDragHandles()); }, + toggleHideCheckedItems() { + const user = Meteor.user(); + user.toggleHideCheckedItems(); + }, toggleSystemMessages() { const user = Meteor.user(); user.toggleSystem(user.hasHiddenSystemMessages()); }, + toggleCardMaximized() { + const user = Meteor.user(); + user.toggleCardMaximized(user.hasCardMaximized()); + }, toggleMinicardLabelText() { const user = Meteor.user(); user.toggleLabelText(user.hasHiddenMinicardLabelText()); @@ -667,28 +921,86 @@ Meteor.methods({ check(limit, Number); Meteor.user().setShowCardsCountAt(limit); }, + changeStartDayOfWeek(startDay) { + check(startDay, Number); + Meteor.user().setStartDayOfWeek(startDay); + }, }); if (Meteor.isServer) { Meteor.methods({ - setCreateUser(fullname, username, password, isAdmin, isActive, email) { + setAllUsersHideSystemMessages() { if (Meteor.user() && Meteor.user().isAdmin) { - check(fullname, String); - check(username, String); - check(password, String); - check(isAdmin, String); - check(isActive, String); - check(email, String); - - const nUsersWithUsername = Users.find({ username }).count(); - const nUsersWithEmail = Users.find({ email }).count(); + // If setting is missing, add it + Users.update( + { + 'profile.hiddenSystemMessages': { + $exists: false, + }, + }, + { + $set: { + 'profile.hiddenSystemMessages': true, + }, + }, + { + multi: true, + }, + ); + // If setting is false, set it to true + Users.update( + { + 'profile.hiddenSystemMessages': false, + }, + { + $set: { + 'profile.hiddenSystemMessages': true, + }, + }, + { + multi: true, + }, + ); + return true; + } else { + return false; + } + }, + setCreateUser( + fullname, + username, + initials, + password, + isAdmin, + isActive, + email, + importUsernames, + userOrgsArray, + userTeamsArray, + ) { + check(fullname, String); + check(username, String); + check(initials, String); + check(password, String); + check(isAdmin, String); + check(isActive, String); + check(email, String); + check(importUsernames, Array); + check(userOrgsArray, Array); + check(userTeamsArray, Array); + if (Meteor.user() && Meteor.user().isAdmin) { + const nUsersWithUsername = Users.find({ + username, + }).count(); + const nUsersWithEmail = Users.find({ + email, + }).count(); if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else if (nUsersWithEmail > 0) { throw new Meteor.Error('email-already-taken'); } else { Accounts.createUser({ - fullname, username, password, isAdmin, @@ -696,30 +1008,59 @@ if (Meteor.isServer) { email: email.toLowerCase(), from: 'admin', }); + const user = + Users.findOne(username) || + Users.findOne({ + username, + }); + if (user) { + Users.update(user._id, { + $set: { + 'profile.fullname': fullname, + importUsernames, + 'profile.initials': initials, + orgs: userOrgsArray, + teams: userTeamsArray, + }, + }); + } } } }, setUsername(username, userId) { + check(username, String); + check(userId, String); if (Meteor.user() && Meteor.user().isAdmin) { - check(username, String); - check(userId, String); - const nUsersWithUsername = Users.find({ username }).count(); + const nUsersWithUsername = Users.find({ + username, + }).count(); if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else { - Users.update(userId, { $set: { username } }); + Users.update(userId, { + $set: { + username, + }, + }); } } }, setEmail(email, userId) { + check(email, String); + check(username, String); if (Meteor.user() && Meteor.user().isAdmin) { if (Array.isArray(email)) { email = email.shift(); } - check(email, String); const existingUser = Users.findOne( - { 'emails.address': email }, - { fields: { _id: 1 } }, + { + 'emails.address': email, + }, + { + fields: { + _id: 1, + }, + }, ); if (existingUser) { throw new Meteor.Error('email-already-taken'); @@ -738,26 +1079,54 @@ if (Meteor.isServer) { } }, setUsernameAndEmail(username, email, userId) { + check(username, String); + check(email, String); + check(userId, String); if (Meteor.user() && Meteor.user().isAdmin) { - check(username, String); if (Array.isArray(email)) { email = email.shift(); } - check(email, String); - check(userId, String); Meteor.call('setUsername', username, userId); Meteor.call('setEmail', email, userId); } }, setPassword(newPassword, userId) { + check(userId, String); + check(newPassword, String); if (Meteor.user() && Meteor.user().isAdmin) { - check(userId, String); - check(newPassword, String); if (Meteor.user().isAdmin) { Accounts.setPassword(userId, newPassword); } } }, + setEmailVerified(email, verified, userId) { + check(email, String); + check(verified, Boolean); + check(userId, String); + if (Meteor.user() && Meteor.user().isAdmin) { + Users.update(userId, { + $set: { + emails: [ + { + address: email, + verified, + }, + ], + }, + }); + } + }, + setInitials(initials, userId) { + check(initials, String); + check(userId, String); + if (Meteor.user() && Meteor.user().isAdmin) { + Users.update(userId, { + $set: { + 'profile.initials': initials, + }, + }); + } + }, // we accept userId, username, email inviteUserToBoard(username, boardId) { check(username, String); @@ -770,7 +1139,9 @@ if (Meteor.isServer) { board && board.members && _.contains(_.pluck(board.members, 'userId'), inviter._id) && - _.where(board.members, { userId: inviter._id })[0].isActive; + _.where(board.members, { + userId: inviter._id, + })[0].isActive; // GitHub issue 2060 //_.where(board.members, { userId: inviter._id })[0].isAdmin; if (!allowInvite) throw new Meteor.Error('error-board-notAMember'); @@ -780,22 +1151,39 @@ if (Meteor.isServer) { const posAt = username.indexOf('@'); let user = null; if (posAt >= 0) { - user = Users.findOne({ emails: { $elemMatch: { address: username } } }); + user = Users.findOne({ + emails: { + $elemMatch: { + address: username, + }, + }, + }); } else { - user = Users.findOne(username) || Users.findOne({ username }); + user = + Users.findOne(username) || + Users.findOne({ + username, + }); } if (user) { if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf'); } else { if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); - if (Settings.findOne({ disableRegistration: true })) { + if ( + Settings.findOne({ + disableRegistration: true, + }) + ) { throw new Meteor.Error('error-user-notCreated'); } // Set in lowercase email before creating account const email = username.toLowerCase(); username = email.substring(0, posAt); - const newUserId = Accounts.createUser({ username, email }); + const newUserId = Accounts.createUser({ + username, + email, + }); if (!newUserId) throw new Meteor.Error('error-user-notCreated'); // assume new user speak same language with inviter if (inviter.profile && inviter.profile.language) { @@ -812,6 +1200,16 @@ if (Meteor.isServer) { board.addMember(user._id); user.addInvite(boardId); + //Check if there is a subtasks board + if (board.subtasksDefaultBoardId) { + const subBoard = Boards.findOne(board.subtasksDefaultBoardId); + //If there is, also add user to that board + if (subBoard) { + subBoard.addMember(user._id); + user.addInvite(subBoard._id); + } + } + try { const params = { user: user.username, @@ -829,7 +1227,28 @@ if (Meteor.isServer) { } catch (e) { throw new Meteor.Error('email-fail', e.message); } - return { username: user.username, email: user.emails[0].address }; + return { + username: user.username, + email: user.emails[0].address, + }; + }, + impersonate(userId) { + check(userId, String); + + if (!Meteor.users.findOne(userId)) + throw new Meteor.Error(404, 'User not found'); + if (!Meteor.user().isAdmin) + throw new Meteor.Error(403, 'Permission denied'); + + ImpersonatedUsers.insert({ adminId: Meteor.user()._id, userId: userId, reason: 'clickedImpersonate' }); + this.setUserId(userId); + }, + isImpersonated(userId) { + check(userId, String); + const isImpersonated = ImpersonatedUsers.findOne({ + userId: userId, + }); + return isImpersonated; }, }); Accounts.onCreateUser((options, user) => { @@ -846,21 +1265,35 @@ if (Meteor.isServer) { } email = email.toLowerCase(); user.username = user.services.oidc.username; - user.emails = [{ address: email, verified: true }]; + user.emails = [ + { + address: email, + verified: true, + }, + ]; const initials = user.services.oidc.fullname - .match(/\b[a-zA-Z]/g) - .join('') + .split(/\s+/) + .reduce((memo, word) => { + return memo + word[0]; + }, '') .toUpperCase(); user.profile = { initials, fullname: user.services.oidc.fullname, - boardView: 'board-view-lists', + boardView: 'board-view-swimlanes', }; user.authenticationMethod = 'oauth2'; // see if any existing user has this email address or username, otherwise create new const existingUser = Meteor.users.findOne({ - $or: [{ 'emails.address': email }, { username: user.username }], + $or: [ + { + 'emails.address': email, + }, + { + username: user.username, + }, + ], }); if (!existingUser) return user; @@ -872,7 +1305,12 @@ if (Meteor.isServer) { existingUser.profile = user.profile; existingUser.authenticationMethod = user.authenticationMethod; - Meteor.users.remove({ _id: existingUser._id }); // remove existing record + Meteor.users.remove({ + _id: user._id, + }); + Meteor.users.remove({ + _id: existingUser._id, + }); // is going to be created again return existingUser; } @@ -911,13 +1349,17 @@ if (Meteor.isServer) { "The invitation code doesn't exist", ); } else { - user.profile = { icode: options.profile.invitationcode }; - user.profile.boardView = 'board-view-lists'; + user.profile = { + icode: options.profile.invitationcode, + }; + user.profile.boardView = 'board-view-swimlanes'; // Deletes the invitation code after the user was created successfully. setTimeout( Meteor.bindEnvironment(() => { - InvitationCodes.remove({ _id: invitationCode._id }); + InvitationCodes.remove({ + _id: invitationCode._id, + }); }), 200, ); @@ -926,19 +1368,59 @@ if (Meteor.isServer) { }); } +const addCronJob = _.debounce( + Meteor.bindEnvironment(function notificationCleanupDebounced() { + // passed in the removeAge has to be a number standing for the number of days after a notification is read before we remove it + const envRemoveAge = + process.env.NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE; + // default notifications will be removed 2 days after they are read + const defaultRemoveAge = 2; + const removeAge = parseInt(envRemoveAge, 10) || defaultRemoveAge; + + SyncedCron.add({ + name: 'notification_cleanup', + schedule: (parser) => parser.text('every 1 days'), + job: () => { + for (const user of Users.find()) { + if (!user.profile || !user.profile.notifications) continue; + for (const notification of user.profile.notifications) { + if (notification.read) { + const removeDate = new Date(notification.read); + removeDate.setDate(removeDate.getDate() + removeAge); + if (removeDate <= new Date()) { + user.removeNotification(notification.activity); + } + } + } + } + }, + }); + + SyncedCron.start(); + }), + 500, +); + if (Meteor.isServer) { // Let mongoDB ensure username unicity Meteor.startup(() => { - allowedSortValues.forEach(value => { + allowedSortValues.forEach((value) => { Lists._collection._ensureIndex(value); }); - Users._collection._ensureIndex({ modifiedAt: -1 }); + Users._collection._ensureIndex({ + modifiedAt: -1, + }); Users._collection._ensureIndex( { username: 1, }, - { unique: true }, + { + unique: true, + }, ); + Meteor.defer(() => { + addCronJob(); + }); }); // OLD WAY THIS CODE DID WORK: When user is last admin of board, @@ -963,7 +1445,7 @@ if (Meteor.isServer) { // counter. // We need to run this code on the server only, otherwise the incrementation // will be done twice. - Users.after.update(function(userId, user, fieldNames) { + Users.after.update(function (userId, user, fieldNames) { // The `starredBoards` list is hosted on the `profile` field. If this // field hasn't been modificated we don't need to run this hook. if (!_.contains(fieldNames, 'profile')) return; @@ -981,8 +1463,12 @@ if (Meteor.isServer) { // b. We use it to find deleted and newly inserted ids by using it in one // direction and then in the other. function incrementBoards(boardsIds, inc) { - boardsIds.forEach(boardId => { - Boards.update(boardId, { $inc: { stars: inc } }); + boardsIds.forEach((boardId) => { + Boards.update(boardId, { + $inc: { + stars: inc, + }, + }); }); } @@ -990,6 +1476,7 @@ if (Meteor.isServer) { incrementBoards(_.difference(newIds, oldIds), +1); }); + // Override getUserId so that we can TODO get the current userId const fakeUserId = new Meteor.EnvironmentVariable(); const getUserId = CollectionHooks.getUserId; CollectionHooks.getUserId = () => { @@ -1005,6 +1492,7 @@ if (Meteor.isServer) { fakeUserId.withValue(doc._id, () => { /* + // Insert the Welcome Board Boards.insert({ title: TAPi18n.__('welcome-board'), @@ -1023,6 +1511,11 @@ if (Meteor.isServer) { }); */ + // Insert Template Container + const Future = require('fibers/future'); + const future1 = new Future(); + const future2 = new Future(); + const future3 = new Future(); Boards.insert( { title: TAPi18n.__('templates'), @@ -1033,7 +1526,9 @@ if (Meteor.isServer) { (err, boardId) => { // Insert the reference to our templates board Users.update(fakeUserId.get(), { - $set: { 'profile.templatesBoardId': boardId }, + $set: { + 'profile.templatesBoardId': boardId, + }, }); // Insert the card templates swimlane @@ -1048,8 +1543,11 @@ if (Meteor.isServer) { (err, swimlaneId) => { // Insert the reference to out card templates swimlane Users.update(fakeUserId.get(), { - $set: { 'profile.cardTemplatesSwimlaneId': swimlaneId }, + $set: { + 'profile.cardTemplatesSwimlaneId': swimlaneId, + }, }); + future1.return(); }, ); @@ -1065,8 +1563,11 @@ if (Meteor.isServer) { (err, swimlaneId) => { // Insert the reference to out list templates swimlane Users.update(fakeUserId.get(), { - $set: { 'profile.listTemplatesSwimlaneId': swimlaneId }, + $set: { + 'profile.listTemplatesSwimlaneId': swimlaneId, + }, }); + future2.return(); }, ); @@ -1082,17 +1583,29 @@ if (Meteor.isServer) { (err, swimlaneId) => { // Insert the reference to out board templates swimlane Users.update(fakeUserId.get(), { - $set: { 'profile.boardTemplatesSwimlaneId': swimlaneId }, + $set: { + 'profile.boardTemplatesSwimlaneId': swimlaneId, + }, }); + future3.return(); }, ); }, ); + // HACK + future1.wait(); + future2.wait(); + future3.wait(); + // End of Insert Template Container }); }); } Users.after.insert((userId, doc) => { + // HACK + doc = Users.findOne({ + _id: doc._id, + }); if (doc.createdThroughApi) { // The admin user should be able to create a user despite disabling registration because // it is two different things (registration and creation). @@ -1100,7 +1613,11 @@ if (Meteor.isServer) { // the disableRegistration check. // Issue : https://github.com/wekan/wekan/issues/1232 // PR : https://github.com/wekan/wekan/pull/1251 - Users.update(doc._id, { $set: { createdThroughApi: '' } }); + Users.update(doc._id, { + $set: { + createdThroughApi: '', + }, + }); return; } @@ -1116,7 +1633,7 @@ if (Meteor.isServer) { if (!invitationCode) { throw new Meteor.Error('error-invitation-code-not-exist'); } else { - invitationCode.boardsToBeInvited.forEach(boardId => { + invitationCode.boardsToBeInvited.forEach((boardId) => { const board = Boards.findOne(boardId); board.addMember(doc._id); }); @@ -1124,8 +1641,16 @@ if (Meteor.isServer) { doc.profile = {}; } doc.profile.invitedBoards = invitationCode.boardsToBeInvited; - Users.update(doc._id, { $set: { profile: doc.profile } }); - InvitationCodes.update(invitationCode._id, { $set: { valid: false } }); + Users.update(doc._id, { + $set: { + profile: doc.profile, + }, + }); + InvitationCodes.update(invitationCode._id, { + $set: { + valid: false, + }, + }); } } }); @@ -1134,12 +1659,14 @@ if (Meteor.isServer) { // USERS REST API if (Meteor.isServer) { // Middleware which checks that API is enabled. - JsonRoutes.Middleware.use(function(req, res, next) { + JsonRoutes.Middleware.use(function (req, res, next) { const api = req.url.startsWith('/api'); if ((api === true && process.env.WITH_API === 'true') || api === false) { return next(); } else { - res.writeHead(301, { Location: '/' }); + res.writeHead(301, { + Location: '/', + }); return res.end(); } }); @@ -1150,11 +1677,35 @@ if (Meteor.isServer) { * @summary returns the current user * @return_type Users */ - JsonRoutes.add('GET', '/api/user', function(req, res) { + JsonRoutes.add('GET', '/api/user', function (req, res) { try { Authentication.checkLoggedIn(req.userId); - const data = Meteor.users.findOne({ _id: req.userId }); + const data = Meteor.users.findOne({ + _id: req.userId, + }); delete data.services; + + // get all boards where the user is member of + let boards = Boards.find( + { + type: 'board', + 'members.userId': req.userId, + }, + { + fields: { + _id: 1, + members: 1, + }, + }, + ); + boards = boards.map((b) => { + const u = b.members.find((m) => m.userId === req.userId); + delete u.userId; + u.boardId = b._id; + return u; + }); + + data.boards = boards; JsonRoutes.sendResult(res, { code: 200, data, @@ -1176,13 +1727,16 @@ if (Meteor.isServer) { * @return_type [{ _id: string, * username: string}] */ - JsonRoutes.add('GET', '/api/users', function(req, res) { + JsonRoutes.add('GET', '/api/users', function (req, res) { try { Authentication.checkUserId(req.userId); JsonRoutes.sendResult(res, { code: 200, - data: Meteor.users.find({}).map(function(doc) { - return { _id: doc._id, username: doc.username }; + data: Meteor.users.find({}).map(function (doc) { + return { + _id: doc._id, + username: doc.username, + }; }), }); } catch (error) { @@ -1200,16 +1754,47 @@ if (Meteor.isServer) { * * @description Only the admin user (the first user) can call the REST API. * - * @param {string} userId the user ID + * @param {string} userId the user ID or username * @return_type Users */ - JsonRoutes.add('GET', '/api/users/:userId', function(req, res) { + JsonRoutes.add('GET', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); - const id = req.params.userId; + let id = req.params.userId; + let user = Meteor.users.findOne({ + _id: id, + }); + if (!user) { + user = Meteor.users.findOne({ + username: id, + }); + id = user._id; + } + + // get all boards where the user is member of + let boards = Boards.find( + { + type: 'board', + 'members.userId': id, + }, + { + fields: { + _id: 1, + members: 1, + }, + }, + ); + boards = boards.map((b) => { + const u = b.members.find((m) => m.userId === id); + delete u.userId; + u.boardId = b._id; + return u; + }); + + user.boards = boards; JsonRoutes.sendResult(res, { code: 200, - data: Meteor.users.findOne({ _id: id }), + data: user, }); } catch (error) { JsonRoutes.sendResult(res, { @@ -1236,18 +1821,27 @@ if (Meteor.isServer) { * @return_type {_id: string, * title: string} */ - JsonRoutes.add('PUT', '/api/users/:userId', function(req, res) { + JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); const id = req.params.userId; const action = req.body.action; - let data = Meteor.users.findOne({ _id: id }); + let data = Meteor.users.findOne({ + _id: id, + }); if (data !== undefined) { if (action === 'takeOwnership') { - data = Boards.find({ - 'members.userId': id, - 'members.isAdmin': true, - }).map(function(board) { + data = Boards.find( + { + 'members.userId': id, + 'members.isAdmin': true, + }, + { + sort: { + sort: 1 /* boards default sorting */, + }, + }, + ).map(function (board) { if (board.hasMember(req.userId)) { board.removeMember(req.userId); } @@ -1260,7 +1854,9 @@ if (Meteor.isServer) { } else { if (action === 'disableLogin' && id !== req.userId) { Users.update( - { _id: id }, + { + _id: id, + }, { $set: { loginDisabled: true, @@ -1269,9 +1865,20 @@ if (Meteor.isServer) { }, ); } else if (action === 'enableLogin') { - Users.update({ _id: id }, { $set: { loginDisabled: '' } }); + Users.update( + { + _id: id, + }, + { + $set: { + loginDisabled: '', + }, + }, + ); } - data = Meteor.users.findOne({ _id: id }); + data = Meteor.users.findOne({ + _id: id, + }); } } JsonRoutes.sendResult(res, { @@ -1305,53 +1912,57 @@ if (Meteor.isServer) { * @return_type {_id: string, * title: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/add', function( - req, - res, - ) { - try { - Authentication.checkUserId(req.userId); - const userId = req.params.userId; - const boardId = req.params.boardId; - const action = req.body.action; - const { isAdmin, isNoComments, isCommentOnly } = req.body; - let data = Meteor.users.findOne({ _id: userId }); - if (data !== undefined) { - if (action === 'add') { - data = Boards.find({ - _id: boardId, - }).map(function(board) { - if (!board.hasMember(userId)) { - board.addMember(userId); - function isTrue(data) { - return data.toLowerCase() === 'true'; + JsonRoutes.add( + 'POST', + '/api/boards/:boardId/members/:userId/add', + function (req, res) { + try { + Authentication.checkUserId(req.userId); + const userId = req.params.userId; + const boardId = req.params.boardId; + const action = req.body.action; + const { isAdmin, isNoComments, isCommentOnly } = req.body; + let data = Meteor.users.findOne({ + _id: userId, + }); + if (data !== undefined) { + if (action === 'add') { + data = Boards.find({ + _id: boardId, + }).map(function (board) { + if (!board.hasMember(userId)) { + board.addMember(userId); + + function isTrue(data) { + return data.toLowerCase() === 'true'; + } + board.setMemberPermission( + userId, + isTrue(isAdmin), + isTrue(isNoComments), + isTrue(isCommentOnly), + userId, + ); } - board.setMemberPermission( - userId, - isTrue(isAdmin), - isTrue(isNoComments), - isTrue(isCommentOnly), - userId, - ); - } - return { - _id: board._id, - title: board.title, - }; - }); + return { + _id: board._id, + title: board.title, + }; + }); + } } + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); } - JsonRoutes.sendResult(res, { - code: 200, - data: query, - }); - } catch (error) { - JsonRoutes.sendResult(res, { - code: 200, - data: error, - }); - } - }); + }, + ); /** * @operation remove_board_member @@ -1370,18 +1981,20 @@ if (Meteor.isServer) { JsonRoutes.add( 'POST', '/api/boards/:boardId/members/:userId/remove', - function(req, res) { + function (req, res) { try { Authentication.checkUserId(req.userId); const userId = req.params.userId; const boardId = req.params.boardId; const action = req.body.action; - let data = Meteor.users.findOne({ _id: userId }); + let data = Meteor.users.findOne({ + _id: userId, + }); if (data !== undefined) { if (action === 'remove') { data = Boards.find({ _id: boardId, - }).map(function(board) { + }).map(function (board) { if (board.hasMember(userId)) { board.removeMember(userId); } @@ -1417,7 +2030,7 @@ if (Meteor.isServer) { * @param {string} password the password of the new user * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/users/', function(req, res) { + JsonRoutes.add('POST', '/api/users/', function (req, res) { try { Authentication.checkUserId(req.userId); const id = Accounts.createUser({ @@ -1450,10 +2063,21 @@ if (Meteor.isServer) { * @param {string} userId the ID of the user to delete * @return_type {_id: string} */ - JsonRoutes.add('DELETE', '/api/users/:userId', function(req, res) { + JsonRoutes.add('DELETE', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); const id = req.params.userId; + // Delete user is enabled, but is still has bug of leaving empty user avatars + // to boards: boards members, card members and assignees have + // empty users. So it would be better to delete user from all boards before + // deleting user. + // See: + // - wekan/client/components/settings/peopleBody.jade deleteButton + // - wekan/client/components/settings/peopleBody.js deleteButton + // - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember' + // that does now remove member from board, card members and assignees correctly, + // but that should be used to remove user from all boards similarly + // - wekan/models/users.js Delete is not enabled Meteor.users.remove({ _id: id }); JsonRoutes.sendResult(res, { code: 200, @@ -1468,6 +2092,38 @@ if (Meteor.isServer) { }); } }); + + /** + * @operation create_user_token + * + * @summary Create a user token + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} userId the ID of the user to create token for. + * @return_type {_id: string} + */ + JsonRoutes.add('POST', '/api/createtoken/:userId', function (req, res) { + try { + Authentication.checkUserId(req.userId); + const id = req.params.userId; + const token = Accounts._generateStampedLoginToken(); + Accounts._insertLoginToken(id, token); + + JsonRoutes.sendResult(res, { + code: 200, + data: { + _id: id, + authToken: token.token, + }, + }); + } catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); } export default Users; diff --git a/models/usersessiondata.js b/models/usersessiondata.js new file mode 100644 index 000000000..7d7189ca1 --- /dev/null +++ b/models/usersessiondata.js @@ -0,0 +1,226 @@ +SessionData = new Mongo.Collection('sessiondata'); + +/** + * A UserSessionData in Wekan. Organization in Trello. + */ +SessionData.attachSchema( + new SimpleSchema({ + _id: { + /** + * the organization id + */ + type: Number, + optional: true, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert && !this.isSet) { + return incrementCounter('counters', 'orgId', 1); + } + }, + }, + userId: { + /** + * userId of the user + */ + type: String, + optional: false, + }, + sessionId: { + /** + * unique session ID + */ + type: String, + optional: false, + }, + totalHits: { + /** + * total number of hits in the last report query + */ + type: Number, + optional: true, + }, + resultsCount: { + /** + * number of results returned + */ + type: Number, + optional: true, + }, + lastHit: { + /** + * the last hit returned from a report query + */ + type: Number, + optional: true, + }, + cards: { + type: [String], + optional: true, + }, + selector: { + type: String, + optional: true, + blackbox: true, + }, + projection: { + type: String, + optional: true, + blackbox: true, + defaultValue: {}, + }, + errorMessages: { + type: [String], + optional: true, + }, + errors: { + type: [Object], + optional: true, + defaultValue: [], + }, + 'errors.$': { + type: new SimpleSchema({ + tag: { + /** + * i18n tag + */ + type: String, + optional: false, + }, + value: { + /** + * value for the tag + */ + type: String, + optional: true, + defaultValue: null, + }, + color: { + type: Boolean, + optional: true, + defaultValue: false, + }, + }), + }, + createdAt: { + /** + * creation date of the team + */ + type: Date, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert) { + return new Date(); + } else if (this.isUpsert) { + return { $setOnInsert: new Date() }; + } else { + this.unset(); + } + }, + }, + modifiedAt: { + type: Date, + denyUpdate: false, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert || this.isUpsert || this.isUpdate) { + return new Date(); + } else { + this.unset(); + } + }, + }, + }), +); + +SessionData.helpers({ + getSelector() { + return SessionData.unpickle(this.selector); + }, + getProjection() { + return SessionData.unpickle(this.projection); + }, +}); + +SessionData.unpickle = pickle => { + return JSON.parse(pickle, (key, value) => { + return unpickleValue(value); + }); +}; + +function unpickleValue(value) { + if (value === null) { + return null; + } else if (typeof value === 'object') { + // eslint-disable-next-line no-prototype-builtins + if (value.hasOwnProperty('$$class')) { + switch (value.$$class) { + case 'RegExp': + return new RegExp(value.source, value.flags); + case 'Date': + return new Date(value.stringValue); + case 'Object': + return unpickleObject(value); + } + } + } + return value; +} + +function unpickleObject(obj) { + const newObject = {}; + Object.entries(obj).forEach(([key, value]) => { + newObject[key] = unpickleValue(value); + }); + return newObject; +} + +SessionData.pickle = value => { + return JSON.stringify(value, (key, value) => { + return pickleValue(value); + }); +}; + +function pickleValue(value) { + if (value === null) { + return null; + } else if (typeof value === 'object') { + switch (value.constructor.name) { + case 'RegExp': + return { + $$class: 'RegExp', + source: value.source, + flags: value.flags, + }; + case 'Date': + return { + $$class: 'Date', + stringValue: String(value), + }; + case 'Object': + return pickleObject(value); + } + } + return value; +} + +function pickleObject(obj) { + const newObject = {}; + Object.entries(obj).forEach(([key, value]) => { + newObject[key] = pickleValue(value); + }); + return newObject; +} + +if (!Meteor.isServer) { + SessionData.getSessionId = () => { + let sessionId = Session.get('sessionId'); + if (!sessionId) { + sessionId = `${String(Meteor.userId())}-${String(Math.random())}`; + Session.set('sessionId', sessionId); + } + + return sessionId; + }; +} + +export default SessionData; diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 9914f8171..2867b12f8 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -15,6 +15,7 @@ export class WekanCreator { cards: {}, lists: {}, swimlanes: {}, + customFields: {}, }; // The object creator Wekan Id, indexed by the object Wekan id // (so we only parse actions once!) @@ -30,6 +31,8 @@ export class WekanCreator { this.lists = {}; // Map of cards Wekan ID => Wekan ID this.cards = {}; + // Map of custom fields Wekan ID => Wekan ID + this.customFields = {}; // Map of comments Wekan ID => Wekan ID this.commentIds = {}; // Map of attachments Wekan ID => Wekan ID @@ -244,18 +247,20 @@ export class WekanCreator { swimlaneId: false, }, ], + presentParentTask: boardToImport.presentParentTask, // Standalone Export has modifiedAt missing, adding modifiedAt to fix it modifiedAt: this._now(boardToImport.modifiedAt), permission: boardToImport.permission, slug: getSlug(boardToImport.title) || 'board', stars: 0, - title: boardToImport.title, + title: Boards.uniqueTitle(boardToImport.title), }; // now add other members if (boardToImport.members) { boardToImport.members.forEach(wekanMember => { - // do we already have it in our list? + // is it defined and do we already have it in our list? if ( + wekanMember.wekanId && !boardToCreate.members.some( member => member.wekanId === wekanMember.wekanId, ) @@ -352,10 +357,40 @@ export class WekanCreator { cardToCreate.members = wekanMembers; } } + // add assignees + if (card.assignees) { + const wekanAssignees = []; + // we can't just map, as some members may not have been mapped + card.assignees.forEach(sourceMemberId => { + if (this.members[sourceMemberId]) { + const wekanId = this.members[sourceMemberId]; + // we may map multiple Wekan members to the same wekan user + // in which case we risk adding the same user multiple times + if (!wekanAssignees.find(wId => wId === wekanId)) { + wekanAssignees.push(wekanId); + } + } + return true; + }); + if (wekanAssignees.length > 0) { + cardToCreate.assignees = wekanAssignees; + } + } // set color if (card.color) { cardToCreate.color = card.color; } + + // add custom fields + if (card.customFields) { + cardToCreate.customFields = card.customFields.map(field => { + return { + _id: this.customFields[field._id], + value: field.value, + }; + }); + } + // insert card const cardId = Cards.direct.insert(cardToCreate); // keep track of Wekan id => Wekan id @@ -440,6 +475,12 @@ export class WekanCreator { } }); } else if (att.file) { + //If attribute type is null or empty string is set, assume binary stream + att.type = + !att.type || att.type.trim().length === 0 + ? 'application/octet-stream' + : att.type; + file.attachData( Buffer.from(att.file, 'base64'), { @@ -481,6 +522,40 @@ export class WekanCreator { return result; } + /** + * Create the Wekan custom fields corresponding to the supplied Wekan + * custom fields. + * @param wekanCustomFields + * @param boardId + */ + createCustomFields(wekanCustomFields, boardId) { + wekanCustomFields.forEach((field, fieldIndex) => { + const fieldToCreate = { + boardIds: [boardId], + name: field.name, + type: field.type, + settings: field.settings, + showOnCard: field.showOnCard, + showLabelOnMiniCard: field.showLabelOnMiniCard, + automaticallyOnCard: field.automaticallyOnCard, + alwaysOnCard: field.alwaysOnCard, + //use date "now" if now created at date is provided (e.g. for very old boards) + createdAt: this._now(this.createdAt.customFields[field._id]), + modifiedAt: field.modifiedAt, + }; + //insert copy of custom field + const fieldId = CustomFields.direct.insert(fieldToCreate); + //set modified date to now + CustomFields.direct.update(fieldId, { + $set: { + modifiedAt: this._now(), + }, + }); + //store mapping of old id to new id + this.customFields[field._id] = fieldId; + }); + } + // Create labels if they do not exist and load this.labels. createLabels(wekanLabels, board) { wekanLabels.forEach(label => { @@ -560,6 +635,35 @@ export class WekanCreator { }); } + createSubtasks(wekanCards) { + wekanCards.forEach(card => { + // get new id of card (in created / new board) + const cardIdInNewBoard = this.cards[card._id]; + + //If there is a mapped parent card, use the mapped card + // this means, the card and parent were in the same source board + //If there is no mapped parent card, use the original parent id, + // this should handle cases where source and parent are in different boards + // Note: This can only handle board cloning (within the same wekan instance). + // When importing boards between instances the IDs are definitely + // lost if source and parent are two different boards + // This is not the place to fix it, the entire subtask system needs to be rethought there. + const parentIdInNewBoard = this.cards[card.parentId] + ? this.cards[card.parentId] + : card.parentId; + + //if the parent card exists, proceed + if (Cards.findOne(parentIdInNewBoard)) { + //set parent id of the card in the new board to the new id of the parent + Cards.direct.update(cardIdInNewBoard, { + $set: { + parentId: parentIdInNewBoard, + }, + }); + } + }); + } + createChecklists(wekanChecklists) { const result = []; wekanChecklists.forEach((checklist, checklistIndex) => { @@ -620,18 +724,24 @@ export class WekanCreator { createChecklistItems(wekanChecklistItems) { wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => { - // Create the checklistItem - const checklistItemTocreate = { - title: checklistitem.title, - checklistId: this.checklists[checklistitem.checklistId], - cardId: this.cards[checklistitem.cardId], - sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex, - isFinished: checklistitem.isFinished, - }; - const checklistItemId = ChecklistItems.direct.insert( - checklistItemTocreate, - ); - this.checklistItems[checklistitem._id] = checklistItemId; + //Check if the checklist for this item (still) exists + //If a checklist was deleted, but items remain, the import would error out here + //Leading to no further checklist items being imported + if (this.checklists[checklistitem.checklistId]) { + // Create the checklistItem + const checklistItemTocreate = { + title: checklistitem.title, + checklistId: this.checklists[checklistitem.checklistId], + cardId: this.cards[checklistitem.cardId], + sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex, + isFinished: checklistitem.isFinished, + }; + + const checklistItemId = ChecklistItems.direct.insert( + checklistItemTocreate, + ); + this.checklistItems[checklistitem._id] = checklistItemId; + } }); } @@ -690,6 +800,11 @@ export class WekanCreator { this.createdAt.swimlanes[swimlaneId] = activity.createdAt; break; } + case 'createCustomField': { + const customFieldId = activity.customFieldId; + this.createdAt.customFields[customFieldId] = activity.createdAt; + break; + } } }); } @@ -840,7 +955,9 @@ export class WekanCreator { const boardId = this.createBoardAndLabels(board); this.createLists(board.lists, boardId); this.createSwimlanes(board.swimlanes, boardId); + this.createCustomFields(board.customFields, boardId); this.createCards(board.cards, boardId); + this.createSubtasks(board.cards); this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId); diff --git a/openapi/generate_openapi.py b/openapi/generate_openapi.py index 54526416f..4b6aa9a27 100644 --- a/openapi/generate_openapi.py +++ b/openapi/generate_openapi.py @@ -15,7 +15,7 @@ err_context = 3 def get_req_body_elems(obj, elems): - if obj.type == 'FunctionExpression': + if obj.type in ['FunctionExpression', 'ArrowFunctionExpression']: get_req_body_elems(obj.body, elems) elif obj.type == 'BlockStatement': for s in obj.body: @@ -249,7 +249,10 @@ class EntryPoint(object): if name.startswith('{'): param_type = name.strip('{}') - if param_type not in ['string', 'number', 'boolean', 'integer', 'array', 'file']: + if param_type == 'Object': + # hope for the best + param_type = 'object' + elif param_type not in ['string', 'number', 'boolean', 'integer', 'array', 'file']: self.warn('unknown type {}\n allowed values: string, number, boolean, integer, array, file'.format(param_type)) try: name, desc = desc.split(maxsplit=1) @@ -463,6 +466,7 @@ class SchemaProperty(object): self.type = 'object' self.blackbox = False self.required = True + imports = {} for p in statement.value.properties: try: if p.key.name == 'type': @@ -474,41 +478,79 @@ class SchemaProperty(object): elif p.key.name == 'allowedValues': self.type = 'enum' - if p.value.type == 'ArrayExpression': - self.enum = [e.value.lower() for e in p.value.elements] - elif p.value.type == 'Identifier': - # tree wide lookout for the identifier - def find_variable(elem, match): - if isinstance(elem, list): - for value in elem: - ret = find_variable(value, match) + self.enum = [] + + def parse_enum(value, enum): + if value.type == 'ArrayExpression': + for e in value.elements: + parse_enum(e, enum) + elif value.type == 'Literal': + enum.append(value.value.lower()) + return + elif value.type == 'Identifier': + # tree wide lookout for the identifier + def find_variable(elem, match): + if isinstance(elem, list): + for value in elem: + ret = find_variable(value, match) + if ret is not None: + return ret + + try: + items = elem.items() + except AttributeError: + return None + except TypeError: + return None + + if (elem.type == 'VariableDeclarator' and + elem.id.name == match): + return elem + elif (elem.type == 'ImportSpecifier' and + elem.local.name == match): + # we have to treat that case in the caller, because we lack + # information of the source of the import at that point + return elem + elif (elem.type == 'ExportNamedDeclaration' and + elem.declaration.type == 'VariableDeclaration'): + ret = find_variable(elem.declaration.declarations, match) if ret is not None: return ret - try: - items = elem.items() - except AttributeError: - return None - except TypeError: + for type, value in items: + ret = find_variable(value, match) + if ret is not None: + if ret.type == 'ImportSpecifier': + # first open and read the import source, if + # we haven't already done so + path = elem.source.value + if elem.source.value.startswith('/'): + script_dir = os.path.dirname(os.path.realpath(__file__)) + path = os.path.abspath(os.path.join('{}/..'.format(script_dir), elem.source.value.lstrip('/'))) + else: + path = os.path.abspath(os.path.join(os.path.dirname(context.path), elem.source.value)) + path += '.js' + + if path not in imports: + imported_context = parse_file(path) + imports[path] = imported_context + imported_context = imports[path] + + # and then re-run the find in the imported file + return find_variable(imported_context.program.body, match) + + return ret + return None - if (elem.type == 'VariableDeclarator' and - elem.id.name == match): - return elem + elem = find_variable(context.program.body, value.name) - for type, value in items: - ret = find_variable(value, match) - if ret is not None: - return ret + if elem is None: + raise TypeError('can not find "{}"'.format(value.name)) - return None + return parse_enum(elem.init, enum) - elem = find_variable(context.program.body, p.value.name) - - if elem.init.type != 'ArrayExpression': - raise TypeError('can not find "{}"'.format(p.value.name)) - - self.enum = [e.value.lower() for e in elem.init.elements] + parse_enum(p.value, self.enum) elif p.key.name == 'blackbox': self.blackbox = True @@ -565,6 +607,9 @@ class SchemaProperty(object): # deal with subschemas if '.' in name: + subschema = name.split('.')[0] + subschema = subschema.capitalize() + if name.endswith('$'): # reference in reference subschema = ''.join([n.capitalize() for n in self.name.split('.')[:-1]]) @@ -579,9 +624,12 @@ class SchemaProperty(object): print(''' {}: type: object'''.format(subschema)) return current_schema + elif '$' in name: + # In the form of 'profile.notifications.$.activity' + subschema = name[:name.index('$') - 1] # 'profile.notifications' + subschema = ''.join([s.capitalize() for s in subschema.split('.')]) - subschema = name.split('.')[0] - schema_name = self.schema.name + subschema.capitalize() + schema_name = self.schema.name + subschema name = name.split('.')[-1] if current_schema != schema_name: @@ -713,7 +761,7 @@ class Schemas(object): # then print the references current = None required_properties = [] - properties = [f for f in self.fields if '.' in f.name and not f.name.endswith('$')] + properties = [f for f in self.fields if '.' in f.name and not '$' in f.name] for prop in properties: current = prop.print_openapi(6, current, required_properties) @@ -724,7 +772,7 @@ class Schemas(object): required_properties = [] # then print the references in the references - for prop in [f for f in self.fields if '.' in f.name and f.name.endswith('$')]: + for prop in [f for f in self.fields if '.' in f.name and '$' in f.name]: current = prop.print_openapi(6, current, required_properties) if required_properties: @@ -754,6 +802,15 @@ class Context(object): return ''.join(self._txt[begin - 1:end]) +def parse_file(path): + try: + # if the file failed, it's likely it doesn't contain a schema + context = Context(path) + except: + return + + return context + def parse_schemas(schemas_dir): schemas = {} @@ -763,11 +820,11 @@ def parse_schemas(schemas_dir): files.sort() for filename in files: path = os.path.join(root, filename) - try: - # if the file failed, it's likely it doesn't contain a schema - context = Context(path) - except: - continue + context = parse_file(path) + + if context is None: + # the file doesn't contain a schema (see above) + continue program = context.program @@ -814,13 +871,21 @@ def parse_schemas(schemas_dir): for d in data] entry_points.extend(schema_entry_points) + end_of_previous_operation = -1 + # try to match JSDoc to the operations for entry_point in schema_entry_points: operation = entry_point.method # POST/GET/PUT/DELETE + + # find all jsdocs that end before the current operation, + # the last item in the list is the one we need jsdoc = [j for j in jsdocs - if j.loc.end.line + 1 == operation.loc.start.line] + if j.loc.end.line + 1 <= operation.loc.start.line and + j.loc.start.line > end_of_previous_operation] if bool(jsdoc): - entry_point.doc = jsdoc[0] + entry_point.doc = jsdoc[-1] + + end_of_previous_operation = operation.loc.end.line except TypeError: logger.warning(context.txt_for(statement)) logger.error('{}:{}-{} can not parse {}'.format(path, @@ -1001,7 +1066,7 @@ def main(): script_dir = os.path.dirname(os.path.realpath(__file__)) parser.add_argument('--release', default='git-master', nargs=1, help='the current version of the API, can be retrieved by running `git describe --tags --abbrev=0`') - parser.add_argument('dir', default='{}/../models'.format(script_dir), nargs='?', + parser.add_argument('dir', default=os.path.abspath('{}/../models'.format(script_dir)), nargs='?', help='the directory where to look for schemas') args = parser.parse_args() diff --git a/openshift/wekan.yml b/openshift/wekan.yml index 28f633007..d71f9064c 100644 --- a/openshift/wekan.yml +++ b/openshift/wekan.yml @@ -64,8 +64,6 @@ objects: name: "${WEKAN_SERVICE_NAME}" sessionAffinity: None type: ClusterIP - status: - loadBalancer: {} - apiVersion: v1 kind: Service metadata: @@ -86,8 +84,6 @@ objects: name: "${DATABASE_SERVICE_NAME}" sessionAffinity: None type: ClusterIP - status: - loadBalancer: {} - apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -276,7 +272,6 @@ objects: lastTriggeredImage: '' type: ImageChange - type: ConfigChange - status: {} - apiVersion: route.openshift.io/v1 kind: Route metadata: @@ -297,15 +292,6 @@ objects: name: wekan weight: 100 wildcardPolicy: None - status: - ingress: - - conditions: - - lastTransitionTime: '2018-08-28T14:45:21Z' - status: 'True' - type: Admitted - host: ${FQDN} - routerName: router - wildcardPolicy: None parameters: - description: The Fully Qualified Hostname (FQDN) of the application displayName: FQDN @@ -323,7 +309,7 @@ parameters: displayName: Database Service Name name: DATABASE_SERVICE_NAME required: true - value: mongodb + value: wekan-mongodb - description: Username for MongoDB user that will be used for accessing the database. displayName: MongoDB Connection Username from: user[A-Z0-9]{3} @@ -356,13 +342,13 @@ parameters: displayName: Version of MongoDB Image name: MONGODB_VERSION required: true - value: '4.0.10' + value: '3.6' - name: WEKAN_SERVICE_NAME displayName: Wekan Service Name value: wekan required: true - name: WEKAN_IMAGE displayName: Wekan Docker Image - value: wekanteam/wekan:latest + value: quay.io/wekan/wekan description: The metabase docker image to use required: true diff --git a/owasp-zap-scan.yml b/owasp-zap-scan.yml new file mode 100644 index 000000000..9338885fe --- /dev/null +++ b/owasp-zap-scan.yml @@ -0,0 +1,5 @@ +steps: + - name: OWASP ZAP Scan + uses: zaproxy/action-baseline@v0.4.0 + with: + target: 'https://boards.wekan.team' diff --git a/package-lock.json b/package-lock.json index 06cb5f86d..90ae68ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,251 +1,7044 @@ { "name": "wekan", - "version": "v3.90.0", - "lockfileVersion": 1, + "version": "v5.38.0", + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", - "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", - "dev": true - }, - "@typescript-eslint/experimental-utils": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", - "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-scope": "^4.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", - "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "1.13.0", - "@typescript-eslint/typescript-estree": "1.13.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "@typescript-eslint/typescript-estree": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", - "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" - }, + "packages": { + "": { + "version": "v5.37.0", + "license": "MIT", "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "@babel/core": "^7.14.6", + "@babel/runtime": "^7.14.6", + "@liradb2000/markdown-it-mermaid": "^0.4.3", + "ajv": "^6.12.6", + "babel-runtime": "^6.26.0", + "bcryptjs": "^2.4.3", + "bson": "^4.4.1", + "bunyan": "^1.8.15", + "core-js": "^2.6.12", + "dompurify": "^2.3.0", + "es6-promise": "^4.2.4", + "exceljs": "^4.2.1", + "fibers": "^5.0.0", + "gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master", + "jszip": "^3.6.0", + "ldapjs": "^2.3.0", + "markdown-it": "^12.1.0", + "markdown-it-emoji": "^2.0.0", + "meteor-node-stubs": "^1.0.3", + "mongodb": "^3.6.9", + "os": "^0.1.1", + "page": "^1.11.5", + "papaparse": "^5.3.1", + "qs": "^6.10.1", + "source-map-support": "^0.5.19" + }, + "devDependencies": { + "babel-plugin-istanbul": "^6.0.0", + "chai": "^4.3.4", + "flatted": "^3.2.0", + "puppeteer": "^10.0.0", + "sinon": "^11.1.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", + "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.6", + "@babel/parser": "^7.14.6", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dependencies": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "dependencies": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", + "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", + "dependencies": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz", + "integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==" + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@liradb2000/markdown-it-mermaid": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@liradb2000/markdown-it-mermaid/-/markdown-it-mermaid-0.4.3.tgz", + "integrity": "sha512-ieUUuwqvc4Gr7GxLCu9V6gkW8VIxX/7sjOoByERGxbRFwQGzbVHuMAu6Y63M+umFwcfxU9bE3mSbHEi60a75GQ==", + "dependencies": { + "mermaid": "^8.10.2", + "npm": "^7.16.0" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.14.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.44.tgz", + "integrity": "sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA==" + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "node_modules/backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "dependencies": { + "precond": "0.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "node_modules/big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bl/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dependencies": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/bson": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", + "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001242", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz", + "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/compress-commons": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz", + "integrity": "sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "dependencies": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "node_modules/d3-brush": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "dependencies": { + "d3-array": "1", + "d3-path": "1" + } + }, + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "node_modules/d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "node_modules/d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "dependencies": { + "d3-array": "^1.1.1" + } + }, + "node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "node_modules/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "dependencies": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "node_modules/d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, + "node_modules/d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "node_modules/d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "dependencies": { + "d3-dsv": "1" + } + }, + "node_modules/d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "node_modules/d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "node_modules/d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "dependencies": { + "d3-color": "1" + } + }, + "node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "node_modules/d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "node_modules/d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "node_modules/d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "dependencies": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "dependencies": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "node_modules/d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "node_modules/d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "node_modules/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "dependencies": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "node_modules/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/dagre-d3": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz", + "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==", + "dependencies": { + "d3": "^5.14", + "dagre": "^0.8.5", + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/dayjs": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", + "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==" + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", - "dev": true - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" } }, - "ansi-escapes": { + "node_modules/denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.883894", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.883894.tgz", + "integrity": "sha512-33idhm54QJzf3Q7QofMgCvIVSd2o9H3kQPWaKT/fhoZh+digc+WSiMhbkeG3iN79WY4Hwr9G05NpbhEVrsOYAg==", + "dev": true + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dompurify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.0.tgz", + "integrity": "sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==" + }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.768", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.768.tgz", + "integrity": "sha512-I4UMZHhVSK2pwt8jOIxTi3GIuc41NkddtKT/hpuxp9GO5UWJgDKTBa4TACppbVAuKtKbMK6BhQZvT5tFF1bcNA==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/entity-decode": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/entity-decode/-/entity-decode-2.0.2.tgz", + "integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==", + "dependencies": { + "he": "^1.1.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exceljs": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", - "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.2.1.tgz", + "integrity": "sha512-EogoTdXH1X1PxqD9sV8caYd1RIfXN3PVlCV+mA/87CgdO2h4X5xAEbr7CaiP8tffz7L4aBFwsdMbjfMXi29NjA==", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.5.0", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fibers": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", + "integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg==", + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flatted": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.0.tgz", + "integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==", + "dev": true + }, + "node_modules/flushwritable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flushwritable/-/flushwritable-1.0.0.tgz", + "integrity": "sha1-PjKNj95BKtR+c44751C00pAENJg=" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/gridfs-stream": { + "version": "1.1.1", + "resolved": "https://github.com/wekan/gridfs-stream/tarball/master", + "integrity": "sha512-vGe0SUuTpDFEkHFyEJEheToH4LYyCb0Kvat2iB6xTU6PdiCsKGi3VXkM1cc7Zda4Ulu7Mg1p9OAWG718xll7Fg==", + "dependencies": { + "flushwritable": "^1.0.0" + }, + "engines": { + "node": ">= 0.4.2" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/khroma": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-1.4.1.tgz", + "integrity": "sha512-+GmxKvmiRuCcUYDgR7g5Ngo0JEDeOsGdNONdU2zsiBQaK4z19Y2NvXqfEDE0ZiIrg45GTZyAnPLVsLZZACYm3Q==" + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ldap-filter": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz", + "integrity": "sha1-KxTGiiqdQQTb28kQocqF/Riel5c=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ldapjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.0.tgz", + "integrity": "sha512-3Rbm3CS7vzTccpP1QnzKCEPok60L/b3BFlWU8r93P5oadCAaqCWEH9Td08crPnw4Ti20W8y0+ZKtFFNzxVu4kA==", + "dependencies": { + "abstract-logging": "^2.0.0", + "asn1": "^0.2.4", + "assert-plus": "^1.0.0", + "backoff": "^2.5.0", + "ldap-filter": "^0.3.3", + "once": "^1.4.0", + "vasync": "^2.2.0", + "verror": "^1.8.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "node_modules/markdown-it": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.1.0.tgz", + "integrity": "sha512-7temG6IFOOxfU0SgzhqR+vr2diuMhyO5uUIEZ3C5NbXhqC9uFUHoU41USYuDFoZRsaY7BEIEei874Z20VMLF6A==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-emoji": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.0.tgz", + "integrity": "sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ==" + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/mermaid": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.11.0.tgz", + "integrity": "sha512-c/SprR4mJ2Pj7A+3mEvva7XrhEkXQJUal7fIyOkMhOhsPX2u5gQjjm5CEhHQ6WdGsqP+yiR+Fcgnd1i8mpFK8w==", + "dependencies": { + "@braintree/sanitize-url": "^3.1.0", + "d3": "^5.7.0", + "dagre": "^0.8.4", + "dagre-d3": "^0.6.4", + "entity-decode": "^2.0.2", + "graphlib": "^2.1.7", + "khroma": "^1.1.0", + "moment-mini": "^2.22.1", + "stylis": "^3.5.2" + } + }, + "node_modules/meteor-node-stubs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.0.3.tgz", + "integrity": "sha512-JQwIWPfM/Oa2x1Ycwn1Q0wVVQ8b0bOLv+qs4RR/D12b5dPktLlPCRhMzWzRPncZVJtfsnKKBgPLdFmJYUqAwHg==", + "bundleDependencies": [ + "assert", + "browserify-zlib", + "buffer", + "console-browserify", + "constants-browserify", + "crypto-browserify", + "domain-browser", + "events", + "https-browserify", + "os-browserify", + "path-browserify", + "process", + "punycode", + "querystring-es3", + "readable-stream", + "stream-browserify", + "stream-http", + "string_decoder", + "timers-browserify", + "tty-browserify", + "url", + "util", + "vm-browserify" + ], + "dependencies": { + "assert": "^1.4.1", + "browserify-zlib": "^0.2.0", + "buffer": "^5.2.1", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.12.0", + "domain-browser": "^1.2.0", + "elliptic": "^6.5.4", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.0", + "process": "^0.11.10", + "punycode": "^2.1.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.3.0", + "stream-browserify": "^2.0.2", + "stream-http": "^3.0.0", + "string_decoder": "^1.2.0", + "timers-browserify": "^2.0.10", + "tty-browserify": "0.0.1", + "url": "^0.11.0", + "util": "^0.11.1", + "vm-browserify": "^1.1.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/asn1.js": { + "version": "5.4.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/assert": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/meteor-node-stubs/node_modules/assert/node_modules/util": { + "version": "0.10.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/bn.js": { + "version": "5.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/brorand": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/browserify-aes": { + "version": "1.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/browserify-cipher": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/browserify-des": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/browserify-rsa": { + "version": "4.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/browserify-sign": { + "version": "4.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/browserify-sign/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/browserify-zlib": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/meteor-node-stubs/node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/meteor-node-stubs/node_modules/buffer-xor": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/builtin-status-codes": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/cipher-base": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/console-browserify": { + "version": "1.2.0", + "inBundle": true + }, + "node_modules/meteor-node-stubs/node_modules/constants-browserify": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/core-util-is": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/create-ecdh": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/meteor-node-stubs/node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/create-hash": { + "version": "1.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/create-hmac": { + "version": "1.1.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/meteor-node-stubs/node_modules/crypto-browserify": { + "version": "3.12.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/meteor-node-stubs/node_modules/des.js": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/diffie-hellman": { + "version": "5.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/domain-browser": { + "version": "1.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/elliptic": { + "version": "6.5.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/elliptic/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/events": { + "version": "3.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/meteor-node-stubs/node_modules/evp_bytestokey": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/hash-base": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/meteor-node-stubs/node_modules/hash-base/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/hash.js": { + "version": "1.1.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/hash.js/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/hmac-drbg": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/https-browserify": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/meteor-node-stubs/node_modules/inherits": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/isarray": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/md5.js": { + "version": "1.3.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/miller-rabin": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/meteor-node-stubs/node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/minimalistic-assert": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/object-assign": { + "version": "4.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/os-browserify": { + "version": "0.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/pako": { + "version": "1.0.11", + "inBundle": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/meteor-node-stubs/node_modules/parse-asn1": { + "version": "5.1.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/path-browserify": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/pbkdf2": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/meteor-node-stubs/node_modules/process": { + "version": "0.11.10", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/process-nextick-args": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/public-encrypt": { + "version": "4.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/punycode": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/meteor-node-stubs/node_modules/querystring": { + "version": "0.2.0", + "inBundle": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/meteor-node-stubs/node_modules/querystring-es3": { + "version": "0.2.1", + "inBundle": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/meteor-node-stubs/node_modules/randombytes": { + "version": "2.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/randomfill": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/readable-stream": { + "version": "3.6.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/meteor-node-stubs/node_modules/readable-stream/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/ripemd160": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/setimmediate": { + "version": "1.0.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/sha.js": { + "version": "2.4.11", + "inBundle": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/meteor-node-stubs/node_modules/stream-browserify": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/stream-browserify/node_modules/readable-stream": { + "version": "2.3.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/meteor-node-stubs/node_modules/stream-browserify/node_modules/readable-stream/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/stream-browserify/node_modules/safe-buffer": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/stream-browserify/node_modules/string_decoder": { + "version": "1.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/stream-http": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/meteor-node-stubs/node_modules/stream-http/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/string_decoder": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/timers-browserify": { + "version": "2.0.12", + "inBundle": true, + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/tty-browserify": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/url": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/meteor-node-stubs/node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/util": { + "version": "0.11.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/meteor-node-stubs/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/meteor-node-stubs/node_modules/vm-browserify": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/meteor-node-stubs/node_modules/xtend": { + "version": "4.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/moment-mini": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz", + "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" + }, + "node_modules/mongodb": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.3", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "optional": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-7.19.1.tgz", + "integrity": "sha512-aN3hZzGkPzKOyhjXtOhnQTGumorFhgpOU6xfuQsF1nJKh4DhsgfOMG4s/SNx56r4xHPvM5m/sk914wzDgKba3A==", + "bundleDependencies": [ + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "ansicolors", + "ansistyles", + "archy", + "byte-size", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "leven", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "pacote", + "parse-conflict-json", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dependencies": { + "@npmcli/arborist": "^2.6.4", + "@npmcli/ci-detect": "^1.2.0", + "@npmcli/config": "^2.2.0", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "archy": "~1.0.0", + "byte-size": "^7.0.1", + "cacache": "^15.2.0", + "chalk": "^4.1.0", + "chownr": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.6.0", + "columnify": "~1.5.4", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "hosted-git-info": "^4.0.2", + "ini": "^2.0.0", + "init-package-json": "^2.0.3", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "leven": "^3.1.0", + "libnpmaccess": "^4.0.2", + "libnpmdiff": "^2.0.4", + "libnpmexec": "^2.0.0", + "libnpmfund": "^1.1.0", + "libnpmhook": "^6.0.2", + "libnpmorg": "^2.0.2", + "libnpmpack": "^2.0.1", + "libnpmpublish": "^4.0.1", + "libnpmsearch": "^3.1.1", + "libnpmteam": "^2.0.3", + "libnpmversion": "^1.2.1", + "make-fetch-happen": "^9.0.3", + "minipass": "^3.1.3", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^7.1.2", + "nopt": "^5.0.0", + "npm-audit-report": "^2.1.5", + "npm-package-arg": "^8.1.5", + "npm-pick-manifest": "^6.1.1", + "npm-profile": "^5.0.3", + "npm-registry-fetch": "^11.0.0", + "npm-user-validate": "^1.0.1", + "npmlog": "~4.1.2", + "opener": "^1.5.2", + "pacote": "^11.3.3", + "parse-conflict-json": "^1.1.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^3.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "tar": "^6.1.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^1.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^2.0.2", + "write-file-atomic": "^3.0.3" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "2.6.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^1.0.2", + "@npmcli/metavuln-calculator": "^1.1.0", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.2", + "bin-links": "^2.2.1", + "cacache": "^15.0.3", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp-infer-owner": "^2.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.1.0", + "npm-pick-manifest": "^6.1.0", + "npm-registry-fetch": "^11.0.0", + "pacote": "^11.2.6", + "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "semver": "^7.3.5", + "tar": "^6.1.0", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "1.3.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "2.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ini": "^2.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "semver": "^7.3.4", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "2.0.9", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^6.0.0", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^6.1.1", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^15.0.5", + "pacote": "^11.1.11", + "semver": "^7.3.2" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "1.3.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "1.8.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^1.0.2", + "@npmcli/promise-spawn": "^1.3.2", + "infer-owner": "^1.0.4", + "node-gyp": "^7.1.0", + "read-package-json-fast": "^2.0.1" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.1.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ajv": { + "version": "6.12.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/ansicolors": { + "version": "0.3.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ansistyles": { + "version": "0.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "1.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/asn1": { + "version": "0.2.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/npm/node_modules/assert-plus": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/asynckit": { + "version": "0.4.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/aws-sign2": { + "version": "0.7.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/aws4": { + "version": "1.11.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/npm/node_modules/bin-links": { + "version": "2.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^4.0.1", + "mkdirp": "^1.0.3", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^3.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/byte-size": { + "version": "7.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "15.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/caseless": { + "version": "0.12.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "3.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/code-point-at": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/colors": { + "version": "1.4.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.5.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "node_modules/npm/node_modules/combined-stream": { + "version": "1.0.8", + "inBundle": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/core-util-is": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/dashdash": { + "version": "1.14.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delayed-stream": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.0.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/ecc-jsbn": { + "version": "0.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/extend": { + "version": "3.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/extsprintf": { + "version": "1.3.0", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/forever-agent": { + "version": "0.6.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "2.7.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/npm/node_modules/gauge/node_modules/aproba": { + "version": "1.2.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/getpass": { + "version": "0.1.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "7.1.7", + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.6", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/har-schema": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/har-validator": { + "version": "5.1.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.0", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/http-signature": { + "version": "1.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "3.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "2.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "npm-package-arg": "^8.1.2", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "^3.0.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "1.1.5", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.4.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/is-typedarray": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isarray": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/isstream": { + "version": "0.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/jsbn": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-schema": { + "version": "0.2.3", + "inBundle": true + }, + "node_modules/npm/node_modules/json-schema-traverse": { + "version": "0.4.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/json-stringify-safe": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/jsprim": { + "version": "1.4.1", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/npm/node_modules/just-diff": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/leven": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "4.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", + "tar": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^2.3.0", + "@npmcli/ci-detect": "^1.3.0", + "@npmcli/run-script": "^1.8.4", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^8.1.2", + "pacote": "^11.3.1", + "proc-log": "^1.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^2.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "2.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^1.8.3", + "npm-package-arg": "^8.1.0", + "pacote": "^11.2.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^3.0.2", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "2.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "1.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^2.0.7", + "@npmcli/run-script": "^1.8.4", + "json-parse-even-better-errors": "^2.3.1", + "semver": "^7.3.5", + "stringify-package": "^1.0.1" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "9.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^5.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/mime-db": { + "version": "1.48.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/mime-types": { + "version": "2.1.31", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "3.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.1.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "1.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "7.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^3.0.2", + "semver": "^7.3.2", + "tar": "^6.0.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "3.0.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "2.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "1.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "4.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "8.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "2.2.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.6", + "ignore-walk": "^3.0.3", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "6.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^8.1.2", + "semver": "^7.3.4" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "5.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^9.0.1", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "4.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/npm/node_modules/number-is-nan": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/oauth-sign": { + "version": "0.9.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/object-assign": { + "version": "4.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "11.3.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^2.0.1", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^1.8.2", + "cacache": "^15.0.5", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.3", + "mkdirp": "^1.0.3", + "npm-package-arg": "^8.0.1", + "npm-packlist": "^2.1.4", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^11.0.0", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.0" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "just-diff": "^3.0.1", + "just-diff-apply": "^3.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/path-parse": { + "version": "1.0.7", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/performance-now": { + "version": "2.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/proc-log": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/process-nextick-args": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/psl": { + "version": "1.8.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/punycode": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/qs": { + "version": "6.5.2", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^3.0.0", + "npm-normalize-package-bin": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "2.3.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/request": { + "version": "2.88.2", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/npm/node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/resolve": { + "version": "1.20.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.6.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.9", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sshpk": { + "version": "1.16.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ssri": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "2.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/stringify-package": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "1.0.4", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/tunnel-agent": { + "version": "0.6.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/tweetnacl": { + "version": "0.14.5", + "inBundle": true, + "license": "Unlicense" + }, + "node_modules/npm/node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "1.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/npm/node_modules/uri-js": { + "version": "4.4.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/uuid": { + "version": "3.4.0", + "inBundle": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^1.0.3" + } + }, + "node_modules/npm/node_modules/verror": { + "version": "1.10.0", + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/object-inspect": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", + "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/os": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", + "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/page": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/page/-/page-1.11.6.tgz", + "integrity": "sha512-P6e2JfzkBrPeFCIPplLP7vDDiU84RUUZMrWdsH4ZBGJ8OosnwFkcUkBHp1DTIjuipLliw9yQn/ZJsXZvarsO+g==", + "dependencies": { + "path-to-regexp": "~1.2.1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/papaparse": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", + "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz", + "integrity": "sha1-szcFwUAjTYc8hyHHuf2LVB7Tr/k=", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", + "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.0.0.tgz", + "integrity": "sha512-AxHvCb9IWmmP3gMW+epxdj92Gglii+6Z4sb+W+zc2hTTu10HF0yg6hGXot5O74uYkVqG3lfDRLfnRpi6WOwi5A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "4.3.1", + "devtools-protocol": "0.0.883894", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.1", + "pkg-dir": "4.2.0", + "progress": "2.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.0.0", + "unbzip2-stream": "1.3.3", + "ws": "7.4.6" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sinon": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.1.tgz", + "integrity": "sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "engines": { + "node": "*" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/unbzip2-stream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", + "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vasync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.0.tgz", + "integrity": "sha1-z951GGChWCLbOxMrxZsRakra8Bs=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "verror": "1.10.0" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/compat-data": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==" + }, + "@babel/core": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", + "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.6", + "@babel/parser": "^7.14.6", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", + "requires": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==" + }, + "@babel/helpers": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", + "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", + "requires": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", + "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==" + }, + "@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", + "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.7", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "@braintree/sanitize-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz", + "integrity": "sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==" + }, + "@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "requires": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "requires": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "requires": { - "type-fest": "^0.5.2" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@liradb2000/markdown-it-mermaid": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@liradb2000/markdown-it-mermaid/-/markdown-it-mermaid-0.4.3.tgz", + "integrity": "sha512-ieUUuwqvc4Gr7GxLCu9V6gkW8VIxX/7sjOoByERGxbRFwQGzbVHuMAu6Y63M+umFwcfxU9bE3mSbHEi60a75GQ==", + "requires": { + "mermaid": "^8.10.2", + "npm": "^7.16.0" + } + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/node": { + "version": "14.14.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.44.tgz", + "integrity": "sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA==" + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - } - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } }, "babel-runtime": { "version": "6.26.0", @@ -272,95 +7065,77 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "bcrypt": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.7.tgz", - "integrity": "sha512-K5UglF9VQvBMHl/1elNyyFvAfOY9Bj+rpKrCSR9sFwcW8FywAYJSRwTURNej5TaAK2TEJkcJ6r6lh1YPmspx5Q==", + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", "requires": { - "nan": "2.14.0", - "node-pre-gyp": "0.13.0" - }, - "dependencies": { - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - } + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" } }, "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -370,221 +7145,134 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" } }, "bson": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.0.3.tgz", - "integrity": "sha512-7uBjjxwOSuGLmoqGI1UXWpDGc0K2WjR7dC6iaOg4iriNZo6M2EEBb8co4dEPJ5ArYCebPMie0ecgX0TWF+ZUrQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.4.1.tgz", + "integrity": "sha512-Uu4OCZa0jouQJCKOk1EmmyqtdWAP5HVLru4lQxTwzJzxT+sJ13lVpEZU/MATDxtHiekWMAL84oQY3Xn1LpJVSg==", "requires": { - "buffer": "^5.1.0", - "long": "^4.0.0" + "buffer": "^5.6.0" } }, "buffer": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", - "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==" + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" + }, "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", "requires": { "dtrace-provider": "~0.8", - "moment": "^2.10.6", + "moment": "^2.19.3", "mv": "~2", "safe-json-stringify": "~1" } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001242", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz", + "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug==" + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, - "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - }, - "dependencies": { - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - } - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -592,311 +7280,473 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "compress-commons": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz", + "integrity": "sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "safe-buffer": "~5.1.1" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" } }, - "cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "d3": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", "requires": { - "assert-plus": "^1.0.0" + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" } }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "requires": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "dagre-d3": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz", + "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==", + "requires": { + "d3": "^5.14", + "dagre": "^0.8.5", + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "dayjs": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", + "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==" }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "type-detect": "^4.0.0" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "devtools-protocol": { + "version": "0.0.883894", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.883894.tgz", + "integrity": "sha512-33idhm54QJzf3Q7QofMgCvIVSd2o9H3kQPWaKT/fhoZh+digc+WSiMhbkeG3iN79WY4Hwr9G05NpbhEVrsOYAg==", "dev": true }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "dompurify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.0.tgz", + "integrity": "sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==" }, "dtrace-provider": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "optional": true, "requires": { - "nan": "^2.10.0" + "nan": "^2.14.0" } }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "requires": { - "is-arrayish": "^0.2.1" + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, - "es-abstract": { - "version": "1.17.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.2.tgz", - "integrity": "sha512-YoKuru3Lyoy7yVTBSH2j7UxTqe/je3dWAruC0sHvZX1GNd5zX8SSLvQqEgO9b3Ex8IW+goFI9arEEsFIbulhOw==", - "dev": true, + "electron-to-chromium": { + "version": "1.3.768", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.768.tgz", + "integrity": "sha512-I4UMZHhVSK2pwt8jOIxTi3GIuc41NkddtKT/hpuxp9GO5UWJgDKTBa4TACppbVAuKtKbMK6BhQZvT5tFF1bcNA==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "once": "^1.4.0" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "entity-decode": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/entity-decode/-/entity-decode-2.0.2.tgz", + "integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==", "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "he": "^1.1.1" } }, "es6-promise": { @@ -904,294 +7754,15 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "eslint-config-meteor": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/eslint-config-meteor/-/eslint-config-meteor-0.0.9.tgz", - "integrity": "sha1-a+IZQguko+oCPbMKhm5g70h2Uvo=", - "dev": true - }, - "eslint-config-prettier": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-3.6.0.tgz", - "integrity": "sha512-ixJ4U3uTLXwJts4rmSVW/lMXjlGwCijhBJHk8iVqKKSifeI0qgFEfWl8L63isfc8Od7EiBALF6BX3jKLluf/jQ==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-import-resolver-meteor": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-meteor/-/eslint-import-resolver-meteor-0.4.0.tgz", - "integrity": "sha1-yGhjhAghIIz4EzxczlGQnCamFWk=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "resolve": "^1.1.6" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz", - "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-meteor": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-meteor/-/eslint-plugin-meteor-5.2.0.tgz", - "integrity": "sha512-bHzs/0BwHdKcBbX7tYrSnBaMG+1i2f1wy8k6H/sBBsERD/yifmBUrNLiPyZkIvyVUeI8OaZw8U9fsMvLP5GhIg==", - "dev": true, - "requires": { - "invariant": "2.2.4" - } - }, - "eslint-plugin-prettier": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", - "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", - "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.0.0" - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "esprima": { "version": "4.0.1", @@ -1199,393 +7770,167 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { + "exceljs": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.2.1.tgz", + "integrity": "sha512-EogoTdXH1X1PxqD9sV8caYd1RIfXN3PVlCV+mA/87CgdO2h4X5xAEbr7CaiP8tffz7L4aBFwsdMbjfMXi29NjA==", "requires": { - "estraverse": "^4.1.0" + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.5.0", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" } }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz", - "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==", + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" } }, "extsprintf": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz", - "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" + }, + "fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "requires": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + } }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", - "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "pend": "~1.2.0" } }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, + "fibers": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz", + "integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg==", "requires": { - "flat-cache": "^2.0.1" + "detect-libc": "^1.0.3" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-parent-dir": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", - "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", - "dev": true - }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.0.tgz", + "integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==", "dev": true }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } + "flushwritable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flushwritable/-/flushwritable-1.0.0.tgz", + "integrity": "sha1-PjKNj95BKtR+c44751C00pAENJg=" }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "requires": { - "minipass": "^2.6.0" - } + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" } }, - "get-own-enumerable-property-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz", - "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==", - "dev": true - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1598,93 +7943,61 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "graceful-fs": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", - "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", - "dev": true + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "requires": { + "lodash": "^4.17.15" + } }, "gridfs-stream": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/gridfs-stream/-/gridfs-stream-0.5.3.tgz", - "integrity": "sha1-wIlnKPo+qD9fo8nO1GGvt6A20Uk=" + "version": "https://github.com/wekan/gridfs-stream/tarball/master", + "integrity": "sha512-vGe0SUuTpDFEkHFyEJEheToH4LYyCb0Kvat2iB6xTU6PdiCsKGi3VXkM1cc7Zda4Ulu7Mg1p9OAWG718xll7Fg==", + "requires": { + "flushwritable": "^1.0.0" + } }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "agent-base": "6", + "debug": "4" } }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", - "dev": true - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1694,53 +8007,14 @@ } }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "inflight": { "version": "1.0.6", @@ -1756,786 +8030,311 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "inquirer": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", - "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", - "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^5.2.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "requires": { - "symbol-observable": "^1.1.0" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "dev": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" } }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" - } - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "ldap-filter": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.2.2.tgz", - "integrity": "sha1-8rhCvguG2jNSeYUFsx68rlkNd9A=", - "requires": { - "assert-plus": "0.1.5" }, "dependencies": { - "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=" + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } } } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "khroma": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-1.4.1.tgz", + "integrity": "sha512-+GmxKvmiRuCcUYDgR7g5Ngo0JEDeOsGdNONdU2zsiBQaK4z19Y2NvXqfEDE0ZiIrg45GTZyAnPLVsLZZACYm3Q==" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ldap-filter": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz", + "integrity": "sha1-KxTGiiqdQQTb28kQocqF/Riel5c=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "ldapjs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz", - "integrity": "sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.0.tgz", + "integrity": "sha512-3Rbm3CS7vzTccpP1QnzKCEPok60L/b3BFlWU8r93P5oadCAaqCWEH9Td08crPnw4Ti20W8y0+ZKtFFNzxVu4kA==", "requires": { - "asn1": "0.2.3", + "abstract-logging": "^2.0.0", + "asn1": "^0.2.4", "assert-plus": "^1.0.0", "backoff": "^2.5.0", - "bunyan": "^1.8.3", - "dashdash": "^1.14.0", - "dtrace-provider": "~0.8", - "ldap-filter": "0.2.2", + "ldap-filter": "^0.3.3", "once": "^1.4.0", - "vasync": "^1.6.4", + "vasync": "^2.2.0", "verror": "^1.8.1" } }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "immediate": "~3.0.5" } }, - "lint-staged": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz", - "integrity": "sha512-AXk40M9DAiPi7f4tdJggwuKIViUplYtVj1os1MVEteW7qOkU50EOehayCfO9TsoGK24o/EsWb41yrEgfJDDjCw==", - "dev": true, + "linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==", "requires": { - "chalk": "^2.3.1", - "commander": "^2.14.1", - "cosmiconfig": "^5.0.2", - "debug": "^3.1.0", - "dedent": "^0.7.0", - "execa": "^0.9.0", - "find-parent-dir": "^0.3.0", - "is-glob": "^4.0.0", - "is-windows": "^1.0.2", - "jest-validate": "^23.5.0", - "listr": "^0.14.1", - "lodash": "^4.17.5", - "log-symbols": "^2.2.0", - "micromatch": "^3.1.8", - "npm-which": "^3.0.1", - "p-map": "^1.1.1", - "path-is-inside": "^1.0.2", - "pify": "^3.0.0", - "please-upgrade-node": "^3.0.2", - "staged-git-files": "1.1.1", - "string-argv": "^0.0.2", - "stringify-object": "^3.2.2" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "uc.micro": "^1.0.1" } }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", - "dev": true, - "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" - }, - "dependencies": { - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - } - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } + "listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", - "dev": true + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - } - } + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" }, - "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", - "dev": true - }, - "loglevel-colored-level-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", - "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "loglevel": "^1.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "long": { + "lodash.isnil": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=" }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "markdown-it": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.1.0.tgz", + "integrity": "sha512-7temG6IFOOxfU0SgzhqR+vr2diuMhyO5uUIEZ3C5NbXhqC9uFUHoU41USYuDFoZRsaY7BEIEei874Z20VMLF6A==", "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } + "markdown-it-emoji": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.0.tgz", + "integrity": "sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ==" }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" }, "memory-pager": { "version": "1.5.0", @@ -2543,58 +8342,92 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "mermaid": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.11.0.tgz", + "integrity": "sha512-c/SprR4mJ2Pj7A+3mEvva7XrhEkXQJUal7fIyOkMhOhsPX2u5gQjjm5CEhHQ6WdGsqP+yiR+Fcgnd1i8mpFK8w==", + "requires": { + "@braintree/sanitize-url": "^3.1.0", + "d3": "^5.7.0", + "dagre": "^0.8.4", + "dagre-d3": "^0.6.4", + "entity-decode": "^2.0.2", + "graphlib": "^2.1.7", + "khroma": "^1.1.0", + "moment-mini": "^2.22.1", + "stylis": "^3.5.2" + } + }, "meteor-node-stubs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-0.4.1.tgz", - "integrity": "sha512-UO2OStvLOKoApmOdIP5eCqoLaa/ritMXRg4ffJVdkNLEsczzPvTjgC0Mxk4cM4R8MZkwll90FYgjDf5qUTJdMA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.0.3.tgz", + "integrity": "sha512-JQwIWPfM/Oa2x1Ycwn1Q0wVVQ8b0bOLv+qs4RR/D12b5dPktLlPCRhMzWzRPncZVJtfsnKKBgPLdFmJYUqAwHg==", "requires": { "assert": "^1.4.1", - "browserify-zlib": "^0.1.4", - "buffer": "^4.9.1", + "browserify-zlib": "^0.2.0", + "buffer": "^5.2.1", "console-browserify": "^1.1.0", "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.7", - "events": "^1.1.1", - "https-browserify": "0.0.1", - "os-browserify": "^0.2.1", - "path-browserify": "0.0.0", - "process": "^0.11.9", - "punycode": "^1.4.1", + "crypto-browserify": "^3.12.0", + "domain-browser": "^1.2.0", + "elliptic": "^6.5.4", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.0", + "process": "^0.11.10", + "punycode": "^2.1.1", "querystring-es3": "^0.2.1", - "readable-stream": "^2.3.6", - "stream-browserify": "^2.0.1", - "stream-http": "^2.8.0", - "string_decoder": "^1.1.0", - "timers-browserify": "^1.4.2", - "tty-browserify": "0.0.0", + "readable-stream": "^3.3.0", + "stream-browserify": "^2.0.2", + "stream-http": "^3.0.0", + "string_decoder": "^1.2.0", + "timers-browserify": "^2.0.10", + "tty-browserify": "0.0.1", "url": "^0.11.0", - "util": "^0.10.3", - "vm-browserify": "0.0.4" + "util": "^0.11.1", + "vm-browserify": "^1.1.0" }, "dependencies": { "asn1.js": { - "version": "4.10.1", + "version": "5.4.1", "bundled": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + } } }, "assert": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true, "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" + }, + "dependencies": { + "util": { + "version": "0.10.3", + "bundled": true, + "requires": { + "inherits": "2.0.1" + } + } } }, "base64-js": { - "version": "1.3.0", + "version": "1.5.1", "bundled": true }, "bn.js": { - "version": "4.11.8", + "version": "5.2.0", "bundled": true }, "brorand": { @@ -2623,49 +8456,57 @@ } }, "browserify-des": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "browserify-rsa": { - "version": "4.0.1", + "version": "4.1.0", "bundled": true, "requires": { - "bn.js": "^4.1.0", + "bn.js": "^5.0.0", "randombytes": "^2.0.1" } }, "browserify-sign": { - "version": "4.0.4", + "version": "4.2.1", "bundled": true, "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "bundled": true + } } }, "browserify-zlib": { - "version": "0.1.4", + "version": "0.2.0", "bundled": true, "requires": { - "pako": "~0.2.0" + "pako": "~1.0.5" } }, "buffer": { - "version": "4.9.1", + "version": "5.7.1", "bundled": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "buffer-xor": { @@ -2685,11 +8526,8 @@ } }, "console-browserify": { - "version": "1.1.0", - "bundled": true, - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "bundled": true }, "constants-browserify": { "version": "1.0.0", @@ -2700,11 +8538,17 @@ "bundled": true }, "create-ecdh": { - "version": "4.0.3", + "version": "4.0.4", "bundled": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + } } }, "create-hash": { @@ -2747,12 +8591,8 @@ "randomfill": "^1.0.3" } }, - "date-now": { - "version": "0.1.4", - "bundled": true - }, "des.js": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true, "requires": { "inherits": "^2.0.1", @@ -2766,6 +8606,12 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + } } }, "domain-browser": { @@ -2773,20 +8619,30 @@ "bundled": true }, "elliptic": { - "version": "6.4.0", + "version": "6.5.4", "bundled": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + }, + "inherits": { + "version": "2.0.4", + "bundled": true + } } }, "events": { - "version": "1.1.1", + "version": "3.3.0", "bundled": true }, "evp_bytestokey": { @@ -2798,23 +8654,30 @@ } }, "hash-base": { - "version": "3.0.4", + "version": "3.1.0", "bundled": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "hash.js": { - "version": "1.1.3", - "bundled": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" }, "dependencies": { "inherits": { - "version": "2.0.3", + "version": "2.0.4", + "bundled": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", "bundled": true } } @@ -2829,15 +8692,11 @@ } }, "https-browserify": { - "version": "0.0.1", + "version": "1.0.0", "bundled": true }, "ieee754": { - "version": "1.1.11", - "bundled": true - }, - "indexof": { - "version": "0.0.1", + "version": "1.2.1", "bundled": true }, "inherits": { @@ -2849,11 +8708,12 @@ "bundled": true }, "md5.js": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "miller-rabin": { @@ -2862,6 +8722,12 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + } } }, "minimalistic-assert": { @@ -2872,31 +8738,35 @@ "version": "1.0.1", "bundled": true }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, "os-browserify": { - "version": "0.2.1", + "version": "0.3.0", "bundled": true }, "pako": { - "version": "0.2.9", + "version": "1.0.11", "bundled": true }, "parse-asn1": { - "version": "5.1.1", + "version": "5.1.6", "bundled": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "path-browserify": { - "version": "0.0.0", + "version": "1.0.1", "bundled": true }, "pbkdf2": { - "version": "3.0.16", + "version": "3.1.1", "bundled": true, "requires": { "create-hash": "^1.1.2", @@ -2911,22 +8781,29 @@ "bundled": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true }, "public-encrypt": { - "version": "4.0.2", + "version": "4.0.3", "bundled": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true + } } }, "punycode": { - "version": "1.4.1", + "version": "2.1.1", "bundled": true }, "querystring": { @@ -2938,7 +8815,7 @@ "bundled": true }, "randombytes": { - "version": "2.0.6", + "version": "2.1.0", "bundled": true, "requires": { "safe-buffer": "^5.1.0" @@ -2953,20 +8830,16 @@ } }, "readable-stream": { - "version": "2.3.6", + "version": "3.6.0", "bundled": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "dependencies": { "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true } } @@ -2980,7 +8853,15 @@ } }, "safe-buffer": { - "version": "5.1.2", + "version": "5.2.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "setimmediate": { + "version": "1.0.5", "bundled": true }, "sha.js": { @@ -2992,44 +8873,77 @@ } }, "stream-browserify": { - "version": "2.0.1", + "version": "2.0.2", "bundled": true, "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "bundled": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "stream-http": { - "version": "2.8.1", + "version": "3.1.1", "bundled": true, "requires": { "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.3", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "bundled": true + } } }, "string_decoder": { - "version": "1.1.1", + "version": "1.3.0", "bundled": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "timers-browserify": { - "version": "1.4.2", + "version": "2.0.12", "bundled": true, "requires": { - "process": "~0.11.0" + "setimmediate": "^1.0.4" } }, - "to-arraybuffer": { - "version": "1.0.1", - "bundled": true - }, "tty-browserify": { - "version": "0.0.0", + "version": "0.0.1", "bundled": true }, "url": { @@ -3047,10 +8961,16 @@ } }, "util": { - "version": "0.10.3", + "version": "0.11.1", "bundled": true, "requires": { - "inherits": "2.0.1" + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "bundled": true + } } }, "util-deprecate": { @@ -3058,45 +8978,15 @@ "bundled": true }, "vm-browserify": { - "version": "0.0.4", - "bundled": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.2", + "bundled": true }, "xtend": { - "version": "4.0.1", + "version": "4.0.2", "bundled": true } } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3110,75 +9000,42 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "requires": { - "minimist": "1.2.5" + "minimist": "^1.2.5" } }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", "optional": true }, + "moment-mini": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz", + "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" + }, "mongodb": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.0.tgz", - "integrity": "sha512-M1usRxQ/Xl/IZuTK4LJXViwzaGkH1CuccH4iXqK46+Nv25Y7bAIawoxEZQBAlMtLQhRKyEzVoBK0NBTY01Zp5Q==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==", "requires": { - "bl": "^2.2.0", - "bson": "^1.1.1", + "bl": "^2.2.1", + "bson": "^1.1.4", "denque": "^1.4.1", - "require_optional": "^1.0.1", + "optional-require": "^1.0.3", "safe-buffer": "^5.1.2", "saslprep": "^1.0.0" }, "dependencies": { "bson": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", - "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" } } }, @@ -3187,12 +9044,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -3202,279 +9053,1863 @@ "mkdirp": "~0.5.1", "ncp": "~2.0.0", "rimraf": "~2.4.0" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "^6.0.1" - } - } } }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "optional": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, "ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-pre-gyp": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", - "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" - }, - "npm-packlist": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", - "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npm-path": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", - "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", - "dev": true, - "requires": { - "which": "^1.2.10" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", - "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", - "dev": true, - "requires": { - "commander": "^2.9.0", - "npm-path": "^2.0.2", - "which": "^1.2.10" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" + "isarray": "0.0.1" } } } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "node-releases": { + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "npm": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-7.19.1.tgz", + "integrity": "sha512-aN3hZzGkPzKOyhjXtOhnQTGumorFhgpOU6xfuQsF1nJKh4DhsgfOMG4s/SNx56r4xHPvM5m/sk914wzDgKba3A==", + "requires": { + "@npmcli/arborist": "^2.6.4", + "@npmcli/ci-detect": "^1.2.0", + "@npmcli/config": "^2.2.0", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "archy": "~1.0.0", + "byte-size": "^7.0.1", + "cacache": "^15.2.0", + "chalk": "^4.1.0", + "chownr": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.6.0", + "columnify": "~1.5.4", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "hosted-git-info": "^4.0.2", + "ini": "^2.0.0", + "init-package-json": "^2.0.3", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "leven": "^3.1.0", + "libnpmaccess": "^4.0.2", + "libnpmdiff": "^2.0.4", + "libnpmexec": "^2.0.0", + "libnpmfund": "^1.1.0", + "libnpmhook": "^6.0.2", + "libnpmorg": "^2.0.2", + "libnpmpack": "^2.0.1", + "libnpmpublish": "^4.0.1", + "libnpmsearch": "^3.1.1", + "libnpmteam": "^2.0.3", + "libnpmversion": "^1.2.1", + "make-fetch-happen": "^9.0.3", + "minipass": "^3.1.3", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^7.1.2", + "nopt": "^5.0.0", + "npm-audit-report": "^2.1.5", + "npm-package-arg": "^8.1.5", + "npm-pick-manifest": "^6.1.1", + "npm-profile": "^5.0.3", + "npm-registry-fetch": "^11.0.0", + "npm-user-validate": "^1.0.1", + "npmlog": "~4.1.2", + "opener": "^1.5.2", + "pacote": "^11.3.3", + "parse-conflict-json": "^1.1.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^3.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "tar": "^6.1.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^1.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^2.0.2", + "write-file-atomic": "^3.0.3" + }, + "dependencies": { + "@npmcli/arborist": { + "version": "2.6.4", + "bundled": true, + "requires": { + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^1.0.2", + "@npmcli/metavuln-calculator": "^1.1.0", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.2", + "bin-links": "^2.2.1", + "cacache": "^15.0.3", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp-infer-owner": "^2.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.1.0", + "npm-pick-manifest": "^6.1.0", + "npm-registry-fetch": "^11.0.0", + "pacote": "^11.2.6", + "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "semver": "^7.3.5", + "tar": "^6.1.0", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/ci-detect": { + "version": "1.3.0", + "bundled": true + }, + "@npmcli/config": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ini": "^2.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "semver": "^7.3.4", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/disparity-colors": { + "version": "1.0.1", + "bundled": true, + "requires": { + "ansi-styles": "^4.3.0" + } + }, + "@npmcli/git": { + "version": "2.0.9", + "bundled": true, + "requires": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^6.0.0", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^6.1.1", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "bundled": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/map-workspaces": { + "version": "1.0.3", + "bundled": true, + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + } + }, + "@npmcli/metavuln-calculator": { + "version": "1.1.1", + "bundled": true, + "requires": { + "cacache": "^15.0.5", + "pacote": "^11.1.11", + "semver": "^7.3.2" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "bundled": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "bundled": true + }, + "@npmcli/node-gyp": { + "version": "1.0.2", + "bundled": true + }, + "@npmcli/package-json": { + "version": "1.0.1", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "@npmcli/promise-spawn": { + "version": "1.3.2", + "bundled": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "1.8.5", + "bundled": true, + "requires": { + "@npmcli/node-gyp": "^1.0.2", + "@npmcli/promise-spawn": "^1.3.2", + "infer-owner": "^1.0.4", + "node-gyp": "^7.1.0", + "read-package-json-fast": "^2.0.1" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "bundled": true + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "6.0.2", + "bundled": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.1.4", + "bundled": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "bundled": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.11.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "2.2.1", + "bundled": true, + "requires": { + "cmd-shim": "^4.0.1", + "mkdirp": "^1.0.3", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^3.0.3" + } + }, + "binary-extensions": { + "version": "2.2.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byte-size": { + "version": "7.0.1", + "bundled": true + }, + "cacache": { + "version": "15.2.0", + "bundled": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "4.1.1", + "bundled": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "2.0.0", + "bundled": true + }, + "cidr-regex": { + "version": "3.1.1", + "bundled": true, + "requires": { + "ip-regex": "^4.1.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.6.0", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "string-width": { + "version": "4.2.2", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "4.1.0", + "bundled": true, + "requires": { + "mkdirp-infer-owner": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "colors": { + "version": "1.4.0", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "common-ancestor-path": { + "version": "1.0.1", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.3.1", + "bundled": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "depd": { + "version": "1.1.2", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "diff": { + "version": "5.0.0", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.13", + "bundled": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "bundled": true + }, + "err-code": { + "version": "2.0.3", + "bundled": true + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.7", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.5", + "bundled": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "4.0.2", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "bundled": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "bundled": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore-walk": { + "version": "3.0.4", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "indent-string": { + "version": "4.0.0", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "2.0.0", + "bundled": true + }, + "init-package-json": { + "version": "2.0.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^8.1.2", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "^3.0.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^3.0.0" + } + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "4.3.0", + "bundled": true + }, + "is-cidr": { + "version": "4.0.2", + "bundled": true, + "requires": { + "cidr-regex": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.4.0", + "bundled": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "is-lambda": { + "version": "1.0.1", + "bundled": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-diff": { + "version": "3.1.1", + "bundled": true + }, + "just-diff-apply": { + "version": "3.0.0", + "bundled": true + }, + "leven": { + "version": "3.1.0", + "bundled": true + }, + "libnpmaccess": { + "version": "4.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0" + } + }, + "libnpmdiff": { + "version": "2.0.4", + "bundled": true, + "requires": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", + "tar": "^6.1.0" + } + }, + "libnpmexec": { + "version": "2.0.0", + "bundled": true, + "requires": { + "@npmcli/arborist": "^2.3.0", + "@npmcli/ci-detect": "^1.3.0", + "@npmcli/run-script": "^1.8.4", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^8.1.2", + "pacote": "^11.3.1", + "proc-log": "^1.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + } + }, + "libnpmfund": { + "version": "1.1.0", + "bundled": true, + "requires": { + "@npmcli/arborist": "^2.5.0" + } + }, + "libnpmhook": { + "version": "6.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + } + }, + "libnpmorg": { + "version": "2.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + } + }, + "libnpmpack": { + "version": "2.0.1", + "bundled": true, + "requires": { + "@npmcli/run-script": "^1.8.3", + "npm-package-arg": "^8.1.0", + "pacote": "^11.2.6" + } + }, + "libnpmpublish": { + "version": "4.0.2", + "bundled": true, + "requires": { + "normalize-package-data": "^3.0.2", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.1" + } + }, + "libnpmsearch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "npm-registry-fetch": "^11.0.0" + } + }, + "libnpmteam": { + "version": "2.0.4", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + } + }, + "libnpmversion": { + "version": "1.2.1", + "bundled": true, + "requires": { + "@npmcli/git": "^2.0.7", + "@npmcli/run-script": "^1.8.4", + "json-parse-even-better-errors": "^2.3.1", + "semver": "^7.3.5", + "stringify-package": "^1.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-fetch-happen": { + "version": "9.0.3", + "bundled": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^5.0.0", + "ssri": "^8.0.0" + } + }, + "mime-db": { + "version": "1.48.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.31", + "bundled": true, + "requires": { + "mime-db": "1.48.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "3.1.3", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.3.3", + "bundled": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "bundled": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "bundled": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true + }, + "mkdirp-infer-owner": { + "version": "2.0.0", + "bundled": true, + "requires": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "bundled": true + }, + "mute-stream": { + "version": "0.0.8", + "bundled": true + }, + "negotiator": { + "version": "0.6.2", + "bundled": true + }, + "node-gyp": { + "version": "7.1.2", + "bundled": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^3.0.2", + "semver": "^7.3.2", + "tar": "^6.0.2", + "which": "^2.0.2" + } + }, + "nopt": { + "version": "5.0.0", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "3.0.2", + "bundled": true, + "requires": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-audit-report": { + "version": "2.1.5", + "bundled": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "npm-bundled": { + "version": "1.1.2", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "4.0.0", + "bundled": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, + "npm-package-arg": { + "version": "8.1.5", + "bundled": true, + "requires": { + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "2.2.2", + "bundled": true, + "requires": { + "glob": "^7.1.6", + "ignore-walk": "^3.0.3", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "6.1.1", + "bundled": true, + "requires": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^8.1.2", + "semver": "^7.3.4" + } + }, + "npm-profile": { + "version": "5.0.4", + "bundled": true, + "requires": { + "npm-registry-fetch": "^11.0.0" + } + }, + "npm-registry-fetch": { + "version": "11.0.0", + "bundled": true, + "requires": { + "make-fetch-happen": "^9.0.1", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.1", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.2", + "bundled": true + }, + "p-map": { + "version": "4.0.0", + "bundled": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pacote": { + "version": "11.3.4", + "bundled": true, + "requires": { + "@npmcli/git": "^2.0.1", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^1.8.2", + "cacache": "^15.0.5", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.3", + "mkdirp": "^1.0.3", + "npm-package-arg": "^8.0.1", + "npm-packlist": "^2.1.4", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^11.0.0", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.0" + } + }, + "parse-conflict-json": { + "version": "1.1.1", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "just-diff": "^3.0.1", + "just-diff-apply": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-parse": { + "version": "1.0.7", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "proc-log": { + "version": "1.0.0", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.1", + "bundled": true + }, + "promise-all-reject-late": { + "version": "1.0.1", + "bundled": true + }, + "promise-call-limit": { + "version": "1.0.1", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "2.0.1", + "bundled": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "psl": { + "version": "1.8.0", + "bundled": true + }, + "punycode": { + "version": "2.1.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "2.0.0", + "bundled": true + }, + "read-package-json": { + "version": "3.0.1", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^3.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-json-fast": { + "version": "2.0.2", + "bundled": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "request": { + "version": "2.88.2", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "tough-cookie": { + "version": "2.5.0", + "bundled": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "resolve": { + "version": "1.20.0", + "bundled": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "3.0.2", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "7.3.5", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.3", + "bundled": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true + }, + "socks": { + "version": "2.6.1", + "bundled": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "5.0.0", + "bundled": true, + "requires": { + "agent-base": "6", + "debug": "4", + "socks": "^2.3.3" + } + }, + "spdx-correct": { + "version": "3.1.1", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.9", + "bundled": true + }, + "sshpk": { + "version": "1.16.1", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.1", + "bundled": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "bundled": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar": { + "version": "6.1.0", + "bundled": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "treeverse": { + "version": "1.0.4", + "bundled": true + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "bundled": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "uri-js": { + "version": "4.4.1", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.4.0", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "walk-up-path": { + "version": "1.0.0", + "bundled": true + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "2.0.2", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "3.0.3", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true + } + } + }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz", + "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==" }, "once": { "version": "1.4.0", @@ -3484,131 +10919,62 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } + "optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" }, "os": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "page": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/page/-/page-1.11.5.tgz", - "integrity": "sha512-0JXUHc7Y8p1cPJQbhZSwaKO3p+bU3Rgny+OM5gJMKHWHvJKan/fsE5RUzEjRQolv9DzPOSVWfSOHz0lLxK19eA==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/page/-/page-1.11.6.tgz", + "integrity": "sha512-P6e2JfzkBrPeFCIPplLP7vDDiU84RUUZMrWdsH4ZBGJ8OosnwFkcUkBHp1DTIjuipLliw9yQn/ZJsXZvarsO+g==", "requires": { "path-to-regexp": "~1.2.1" } }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "papaparse": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", + "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==" }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { @@ -3616,109 +10982,33 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, "path-to-regexp": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.2.1.tgz", "integrity": "sha1-szcFwUAjTYc8hyHHuf2LVB7Tr/k=", "requires": { "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } } }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "^2.1.0" - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "pre-commit": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", - "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "spawn-sync": "^1.0.15", - "which": "1.2.x" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "find-up": "^4.0.0" } }, "precond": { @@ -3726,79 +11016,10 @@ "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" }, - "prelude-ls": { + "printj": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-eslint": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", - "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", - "dev": true, - "requires": { - "@typescript-eslint/parser": "^1.10.2", - "common-tags": "^1.4.0", - "core-js": "^3.1.4", - "dlv": "^1.1.0", - "eslint": "^5.0.0", - "indent-string": "^4.0.0", - "lodash.merge": "^4.6.0", - "loglevel-colored-level-prefix": "^1.0.0", - "prettier": "^1.7.0", - "pretty-format": "^23.0.1", - "require-relative": "^0.8.7", - "typescript": "^3.2.1", - "vue-eslint-parser": "^2.0.2" - }, - "dependencies": { - "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } - } - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { "version": "2.0.1", @@ -3806,190 +11027,126 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", + "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "puppeteer": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-10.0.0.tgz", + "integrity": "sha512-AxHvCb9IWmmP3gMW+epxdj92Gglii+6Z4sb+W+zc2hTTu10HF0yg6hGXot5O74uYkVqG3lfDRLfnRpi6WOwi5A==", + "dev": true, "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.5", - "strip-json-comments": "~2.0.1" + "debug": "4.3.1", + "devtools-protocol": "0.0.883894", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.1", + "pkg-dir": "4.2.0", + "progress": "2.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.0.0", + "unbzip2-stream": "1.3.3", + "ws": "7.4.6" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "side-channel": "^1.0.4" } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "requires": { + "minimatch": "^3.0.4" } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", - "dev": true - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, - "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "requires": { - "glob": "^7.1.3" + "glob": "^6.0.1" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, "safe-buffer": { "version": "5.1.2", @@ -4002,15 +11159,6 @@ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4025,250 +11173,91 @@ "sparse-bitfield": "^3.0.3" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "requires": { + "xmlchars": "^2.2.0" + } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "sinon": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.1.tgz", + "integrity": "sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" + "has-flag": "^4.0.0" } } } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -4278,281 +11267,87 @@ "memory-pager": "^1.0.2" } }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "staged-git-files": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.1.tgz", - "integrity": "sha512-H89UNKr1rQJvI1c/PIR3kiAMBV23yvR7LItZiV74HWZwzt7f3YHuujJ9nJZlt58WlFox7XQsOahexwk7nTe69A==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "safe-buffer": "~5.2.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, - "string-argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", - "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, - "table": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz", - "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, "dependencies": { - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { + "bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } } } }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4560,193 +11355,124 @@ "dev": true }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" + "rimraf": "^3.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { - "is-buffer": "^1.1.5" + "glob": "^7.1.3" } } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "unbzip2-stream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", + "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "buffer": "^5.2.1", + "through": "^2.3.8" } }, - "type-fest": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", - "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, + "unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" }, "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vasync": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz", - "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.0.tgz", + "integrity": "sha1-z951GGChWCLbOxMrxZsRakra8Bs=", "requires": { - "verror": "1.6.0" - }, - "dependencies": { - "verror": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz", - "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=", - "requires": { - "extsprintf": "1.2.0" - } - } + "verror": "1.10.0" } }, "verror": { @@ -4759,158 +11485,42 @@ "extsprintf": "^1.2.0" } }, - "vue-eslint-parser": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", - "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.2", - "esquery": "^1.0.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "requires": {} + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, - "xss": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.6.tgz", - "integrity": "sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==", + "zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "requires": { - "commander": "^2.9.0", - "cssfilter": "0.0.10" + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/package.json b/package.json index d6411ffce..c955e5560 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,8 @@ { "name": "wekan", - "version": "v3.90.0", + "version": "v5.38.0", "description": "Open-Source kanban", "private": true, - "scripts": { - "lint": "eslint --cache --ext .js --ignore-path .eslintignore .", - "lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .", - "lint:staged": "lint-staged", - "prettify": "prettier --write '**/*.js' '**/*.jsx'", - "test": "npm run lint" - }, - "lint-staged": { - "*.js": [ - "meteor npm run prettify", - "meteor npm run lint:eslint:fix", - "git add --force" - ], - "*.jsx": [ - "meteor npm run prettify", - "meteor npm run lint:eslint:fix", - "git add --force" - ], - "*.json": [ - "prettier --write", - "git add --force" - ] - }, - "pre-commit": "lint:staged", - "eslintConfig": { - "extends": "@meteorjs/eslint-config-meteor" - }, "repository": { "type": "git", "url": "git+https://github.com/wekan/wekan.git" @@ -40,35 +13,40 @@ }, "homepage": "https://wekan.github.io", "devDependencies": { - "eslint": "^5.16.0", - "eslint-config-meteor": "0.0.9", - "eslint-config-prettier": "^3.6.0", - "eslint-import-resolver-meteor": "^0.4.0", - "eslint-plugin-import": "^2.20.0", - "eslint-plugin-meteor": "^5.1.0", - "eslint-plugin-prettier": "^3.1.2", - "lint-staged": "^7.3.0", - "pre-commit": "^1.2.2", - "prettier": "^1.19.1", - "prettier-eslint": "^9.0.1" + "babel-plugin-istanbul": "^6.0.0", + "chai": "^4.3.4", + "flatted": "^3.2.0", + "puppeteer": "^10.0.0", + "sinon": "^11.1.1" }, "dependencies": { - "@babel/runtime": "^7.8.4", - "ajv": "^5.0.0", + "@babel/core": "^7.14.6", + "@babel/runtime": "^7.14.6", + "@liradb2000/markdown-it-mermaid": "^0.4.3", + "ajv": "^6.12.6", "babel-runtime": "^6.26.0", - "bcrypt": "^3.0.7", - "bson": "^4.0.3", - "bunyan": "^1.8.12", + "bcryptjs": "^2.4.3", + "bson": "^4.4.1", + "bunyan": "^1.8.15", + "core-js": "^2.6.12", + "dompurify": "^2.3.0", "es6-promise": "^4.2.4", - "flatted": "^2.0.1", - "gridfs-stream": "^0.5.3", - "ldapjs": "^1.0.2", - "meteor-node-stubs": "^0.4.1", - "mongodb": "^3.5.0", + "exceljs": "^4.2.1", + "fibers": "^5.0.0", + "gridfs-stream": "https://github.com/wekan/gridfs-stream/tarball/master", + "jszip": "^3.6.0", + "ldapjs": "^2.3.0", + "markdown-it": "^12.1.0", + "markdown-it-emoji": "^2.0.0", + "meteor-node-stubs": "^1.0.3", + "mongodb": "^3.6.9", "os": "^0.1.1", "page": "^1.11.5", - "qs": "^6.9.1", - "source-map-support": "^0.5.16", - "xss": "^1.0.6" + "papaparse": "^5.3.1", + "qs": "^6.10.1", + "source-map-support": "^0.5.19" + }, + "meteor": { + "testModule": "tests/main.js" } } diff --git a/packages/markdown/.gitignore b/packages/markdown/.gitignore deleted file mode 100755 index 677a6fc26..000000000 --- a/packages/markdown/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.build* diff --git a/packages/markdown/README.md b/packages/markdown/README.md old mode 100755 new mode 100644 diff --git a/packages/markdown/markdown.js b/packages/markdown/markdown.js deleted file mode 100755 index bb015cc4a..000000000 --- a/packages/markdown/markdown.js +++ /dev/null @@ -1,9 +0,0 @@ -var mark = marked; - -mark.setOptions({ - gfm: true, - tables: true, - breaks: true -}); - -Markdown = mark; diff --git a/packages/markdown/marked/LICENSE.md b/packages/markdown/marked/LICENSE.md deleted file mode 100644 index 64b41a0e4..000000000 --- a/packages/markdown/marked/LICENSE.md +++ /dev/null @@ -1,43 +0,0 @@ -# License information - -## Contribution License Agreement - -If you contribute code to this project, you are implicitly allowing your code -to be distributed under the MIT license. You are also implicitly verifying that -all code is your original work. `</legalese>` - -## Marked - -Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -## Markdown - -Copyright © 2004, John Gruber -http://daringfireball.net/ -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. diff --git a/packages/markdown/marked/README.md b/packages/markdown/marked/README.md deleted file mode 100644 index e93504ca9..000000000 --- a/packages/markdown/marked/README.md +++ /dev/null @@ -1,76 +0,0 @@ -<a href="https://marked.js.org"> - <img width="60px" height="60px" src="https://marked.js.org/img/logo-black.svg" align="right" /> -</a> - -# Marked - -[![npm](https://badgen.net/npm/v/marked)](https://www.npmjs.com/package/marked) -[![gzip size](https://badgen.net/badgesize/gzip/https://cdn.jsdelivr.net/npm/marked/marked.min.js)](https://cdn.jsdelivr.net/npm/marked/marked.min.js) -[![install size](https://badgen.net/packagephobia/install/marked)](https://packagephobia.now.sh/result?p=marked) -[![downloads](https://badgen.net/npm/dt/marked)](https://www.npmjs.com/package/marked) -[![dep](https://badgen.net/david/dep/markedjs/marked?label=deps)](https://david-dm.org/markedjs/marked) -[![dev dep](https://badgen.net/david/dev/markedjs/marked?label=devDeps)](https://david-dm.org/markedjs/marked?type=dev) -[![travis](https://badgen.net/travis/markedjs/marked)](https://travis-ci.org/markedjs/marked) -[![snyk](https://snyk.io/test/npm/marked/badge.svg)](https://snyk.io/test/npm/marked) - -- ⚡ built for speed -- ⬇️ low-level compiler for parsing markdown without caching or blocking for long periods of time -- ⚖️ light-weight while implementing all markdown features from the supported flavors & specifications -- 🌐 works in a browser, on a server, or from a command line interface (CLI) - -## Demo - -Checkout the [demo page](https://marked.js.org/demo/) to see marked in action ⛹️ - -## Docs - -Our [documentation pages](https://marked.js.org) are also rendered using marked 💯 - -Also read about: - -* [Options](https://marked.js.org/#/USING_ADVANCED.md) -* [Extensibility](https://marked.js.org/#/USING_PRO.md) - -## Installation - -**CLI:** `npm install -g marked` - -**In-browser:** `npm install marked` - -## Usage - -### Warning: 🚨 Marked does not [sanitize](https://marked.js.org/#/USING_ADVANCED.md#options) the output HTML. Please use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! 🚨 - -**CLI** - -``` bash -$ marked -o hello.html -hello world -^D -$ cat hello.html -<p>hello world</p> -``` - -**Browser** - -```html -<!doctype html> -<html> -<head> - <meta charset="utf-8"/> - <title>Marked in the browser - - -
- - - - -``` - -## License - -Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License) diff --git a/packages/markdown/marked/SECURITY.md b/packages/markdown/marked/SECURITY.md deleted file mode 100644 index 4a2e1cb43..000000000 --- a/packages/markdown/marked/SECURITY.md +++ /dev/null @@ -1,10 +0,0 @@ -# Security Policy - -The only completely secure system is the one that doesn't exist in the first place. -Having said that, we take the security of Marked very seriously. - -## Reporting a Vulnerability - -Please disclose potential security issues by email to the project [committers](https://marked.js.org/#/AUTHORS.md) as well as the [listed owners within NPM](https://docs.npmjs.com/cli/owner). -We will provide an initial assessment of security reports within 48 hours and should apply patches within 2 weeks -(also, feel free to contribute a fix for the issue). diff --git a/packages/markdown/marked/docs/.eslintrc.json b/packages/markdown/marked/docs/.eslintrc.json deleted file mode 100644 index 6ee10878c..000000000 --- a/packages/markdown/marked/docs/.eslintrc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": "standard", - "plugins": [ - "standard" - ], - "parserOptions": { - "ecmaVersion": 5, - "sourceType": "script" - }, - "rules": { - "semi": ["error", "always"], - "indent": ["error", 2, { - "SwitchCase": 1, - "VariableDeclarator": { "var": 2 }, - "outerIIFEBody": 0 - }], - "operator-linebreak": ["error", "before", { "overrides": { "=": "after" } }], - "space-before-function-paren": ["error", "never"], - "no-cond-assign": "off", - "no-useless-escape": "off", - "one-var": "off", - "no-control-regex": "off", - "no-prototype-builtins": "off", - - "prefer-const": "off", - "no-var": "off" - }, - "env": { - "node": true, - "browser": true, - "amd": true - } -} diff --git a/packages/markdown/marked/docs/AUTHORS.md b/packages/markdown/marked/docs/AUTHORS.md deleted file mode 100644 index 87ab522f9..000000000 --- a/packages/markdown/marked/docs/AUTHORS.md +++ /dev/null @@ -1,269 +0,0 @@ -# Authors - -Marked takes an encompassing approach to its community. As such, you can think of these as [concentric circles](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951), where each group encompasses the following groups. - - - - - - - - - - - - - - -
- - - -
- Christopher Jeffrey -
Original Author
- Started the fire -
- - - -
- Josh Bruce -
Publisher
- Release Wrangler; Humaning Helper; Heckler of Hypertext -
- - - -
- Steven -
Publisher
- Release Wrangler; Dr. Docs; Open source, of course; GitHub Guru; Humaning Helper -
- - - -
- Jamie Davis -
Committer
- Seeker of Security -
- - - -
- Tony Brix -
Publisher
- Release Wrangler; Titan of the test harness; Dr. DevOps -
-   -
- - - - - - - - - - - - - - - - -
- - - -
- Brandon der Blätter -
Contributor
- Curious Contributor -
- - - -
- Carlos Valle -
Contributor
- Maker of the Marked mark from 2018 to present -
- - - -
- Federico Soave -
Contributor
- Regent of the Regex; Master of Marked -
- - - -
- Karen Yavine -
Contributor
- Snyk's Security Saint -
- - - -
- Костя Третяк -
Contributor
- -
- - - -
- Tom Theisen -
Contributor
- Defibrillator -
- - - -
- Mateus Craveiro -
Contributor
- Defibrillator -
-
- -## Publishers - -Publishers are admins who also have the responsibility, privilege, and burden of publishing the new releases to NPM and performing outreach and external stakeholder communications. Further, when things go pear-shaped, they're the ones taking most of the heat. Finally, when things go well, they're the primary ones praising the contributors who made it possible. - -(In other words, while Admins are focused primarily on the internal workings of the project, Publishers are focused on internal *and* external concerns.) - -**Should not exceed 2:** Having more people with the authority to publish a release can quickly turn into a consensus seeking nightmare (design by committee). Having only one is preferred (Directly Responsible Individual); however, given the nature of the project and its history, having an immediate fallback, and a potential deep fallback (Original author) is probably a good idea. - -[Details on badges](#badges) - -## Admins - -Admins are committers who also have the responsibility, privilege, and burden of selecting committers and making sure the project itself runs smoothly, which includes community maintenance, governance, dispute resolution, and so on. (Letting the contributors easily enter into, and work within, the project to begin contributing, with as little friction as possible.) - -**Should not exceed 3:** When there are too many people with the ability to resolve disputes, the dispute itself can quickly turn into a dispute amongst the admins themselves; therefore, we want this group to be small enough to commit to action and large enough to not put too much burden on one person. (Should ensure faster resolution and responsiveness.) - -To be listed: Admins are usually selected from the pool of committers (or they volunteer, using the same process) who demonstrate good understanding of the marked culture, operations, and do their best to help new contributors get up to speed on how to contribute effectively to the project. - -To be removed: You can remove yourself through the [GitHub UI](https://help.github.com/articles/removing-yourself-from-a-collaborator-s-repository/). - -[Details on badges](#badges) - -## Committers - -Committers are contributors who also have the responsibility, privilege, some might even say burden of being able to review and merge contributions (just usually not their own). - -A note on "decision making authority". This is related to submitting PRs and the [advice process](http://www.reinventingorganizationswiki.com/Decision_Making). The person marked as having decision making authority over a certain area should be sought for advice in that area before committing to a course of action. - -**Should not exceed 5:** For larger PRs affecting more of the codebase and, most likely, review by more people, we try to keep this pool small and responsive and let those with decision making authority have final say without negative repercussions from the other committers. - -To be listed: Committers are usually selected (or they volunteer, using the same process) from contributors who enter the discussions regarding the future direction of Marked (maybe even doing informal reviews of contributions despite not being able to merge them yourself). - -To be removed: You can remove yourself through the [GitHub UI](https://help.github.com/articles/removing-yourself-from-a-collaborator-s-repository/). - -A note on volunteering: - -1. Please do not volunteer unless you believe you can demonstrate to your peers you can do the work required. -2. Please do not overcommit yourself; we count on those committed to the project to be responsive. Really consider, with all you have going on, whether you able to really commit to it. -3. Don't let the previous frighten you away, it can always be changed later by you or your peers. - -[Details on badges](#badges) - -## Contributors - -Contributors are users who submit a [PR](https://github.com/markedjs/marked/pulls), [Issue](https://github.com/markedjs/marked/issues), or collaborate in making Marked a better product and experience for all the users. - -To be listed: make a contribution and, if it has significant impact, the committers may be able to add you here. - -To be removed: please let us know or submit a PR. - -[Details on badges](#badges) - -## Users - -Users are anyone using Marked in some fashion, without them, there's no reason for us to exist. - -|Individual or Organization |Website |Project |Submitted by | -|:--------------------------|:-----------------------|:------------------------------------|:---------------------------------------------------| -|MarkedJS |https://marked.js.org |https://github.com/markedjs/marked |The marked committers | - -To be listed: All fields are optional. Contact any of the committers or, more timely, submit a pull request with the following (using the first row as an example): - -- **Individual or Organization:** The name you would like associated with the record. -- **Website:** A URL to a standalone website for the project. -- **Project:** A URL for the repository of the project using marked. -- **Submitted by:** The name and optional honorifics for the person adding the listing. - -To be removed: Same as above. Only instead of requesting addition request deletion or delete the row yourself. - -

Badges

- -Badges? You don't *need* no stinkin' badges. - -Movie references aside. (It was either that or, "Let's play a game", but that would have been creepy…that's why it will most likely come later.) - -Badges? If you *want* 'em, we got 'em, and here's how you get 'em (and…dramatic pause…why not two dramatic pauses for emphasis?… how they can be taken away). - -- [ ] Add the appropriate badge to the desired contributor in the desired column of this page, even if they're not listed here yet. -- [ ] Submit a PR (we're big on PRs around here, if you haven't noticed, help us help you). -- [ ] Follow the instructions for submitting a badge PR. (There are more details to find within. Come on. Everybody likes surprises, right? No? Actually, we just try to put documentation where it belongs, closer to the code and part of the sequence of events.) - -### Badges at play: - -
-
Curious Contributor
-
A contributor with less than one year on this page who is actively engaged in submitting PRs, Issues, making recommendations, sharing thoughts…without being too annoying about it (let's be clear, submitting 100 Issues recommending the Marked Committers send everyone candy is trying for the badge, not honestly earning it).
-
Dr. DevOps
-
-

Someone who understands and contributes to improving the developer experience and flow of Marked into the world.

-
- "The main characteristic of the DevOps movement is to strongly advocate automation and monitoring at all steps of software construction, from integration, testing, releasing to deployment and infrastructure management. DevOps aims at shorter development cycles, increased deployment frequency, more dependable releases, in close alignment with business objectives." ~ Wikipedia -
-
-
Dr. Docs
-
Someone who has contributed a great deal to the creation and maintenance of the non-code areas of marked.
-
Eye for the CLI
-
At this point? Pretty much anyone who can update that `man` file to the current Marked version without regression in the CLI tool itself.
-
GitHub Guru
-
Someone who always seems to be able to tell you easier ways to do things with GitHub.
-
Humaning Helper
-
Someone who goes out of their way to help contributors feel welcomed and valued. Further, someone who takes the extra steps(s) necessary to help new contributors get up to speed. Finally, they maintain composure even in times of disagreement and dispute resolution.
-
Heckler of Hypertext
-
Someone who demonstrates an esoteric level of knowledge when it comes to HTML. In other words, someone who says things like, "Did you know most Markdown flavors don't have a way to render a description list (`dl`)? All the more reason Markdown `!==` HTML."
-
Markdown Maestro
-
You know that person who knows about way too many different flavors of Markdown? The one who maybe seems a little too obsessed with the possibilities of Markdown beyond HTML? Come on. You know who they are. Or, at least you could, if you give them this badge.
-
Master of Marked
-
Someone who demonstrates they know the ins and outs of the codebase for Marked.
-
Open source, of course
-
Someone who advocates for and has a proven understanding of how to operate within open source communities.
-
Regent of the Regex
-

Can you demonstrate you understand the following without Google and Stackoverflow?

-

/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/

-

Because this author can't yet. That's who gets these.

-
-
Seeker of Security
-
Someone who has demonstrated a high degree of expertise or authority when it comes to software security.
-
Titan of the Test Harness
-
Someone who demonstrates high-levels of understanding regarding Marked's test harness.
-
Totally Tron
-
Someone who demonstrates they are willing and able to "fight for the users", both developers dependent on marked to do their jobs as well as end-users interacting with the output (particularly in the realm of those with the disabilities).
-
- -### Special badges that come with the job: - -
-
Defibrillator
-
A contributor who stepped up to help bring Marked back to life by contributing solutions to help Marked pass when compared against the CommonMark and GitHub Flavored Markdown specifications.
-
Maker of the Marked mark
-
This badge is given to the person or organization credited with creating the logo (or logotype) used in Marked communications for a given period of time. **Maker of the Marked mark from 2017 to present**, for example.
-
Release Wrangler
-
This is a badge given to all Publishers.
-
Snyk's Security Saint
-
This is a badge given to whomever primarily reaches out from Snyk to let us know about security issues.
-
diff --git a/packages/markdown/marked/docs/CNAME b/packages/markdown/marked/docs/CNAME deleted file mode 100644 index c92fdfcbd..000000000 --- a/packages/markdown/marked/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -marked.js.org diff --git a/packages/markdown/marked/docs/CODE_OF_CONDUCT.md b/packages/markdown/marked/docs/CODE_OF_CONDUCT.md deleted file mode 100644 index 5335399ba..000000000 --- a/packages/markdown/marked/docs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,74 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to block temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team by submitting a PR with changes to the [AUTHORS](#/AUTHORS.md) page (or emailing josh@8fold.com). All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version [1.4][version]. - -[homepage]: https://www.contributor-covenant.org/ -[version]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/packages/markdown/marked/docs/CONTRIBUTING.md b/packages/markdown/marked/docs/CONTRIBUTING.md deleted file mode 100644 index f5ec82b6a..000000000 --- a/packages/markdown/marked/docs/CONTRIBUTING.md +++ /dev/null @@ -1,95 +0,0 @@ -# Contributing to Marked - -- [ ] Fork `markedjs/marked`. -- [ ] Clone the library locally using GitHub Desktop or the command line. -- [ ] Make sure you are on the `master` branch. -- [ ] Be sure to run `npm install` or `npm update`. -- [ ] Create a branch. -- [ ] Update code in `src` folder. (`lib` folder is for auto compiled code) -- [ ] Run `npm run test:all`, fix any broken things (for linting, you can run `npm run lint` to have the linter fix them for you). -- [ ] Run `npm run build:reset` to remove changes to compiled files. -- [ ] Submit a Pull Request. - -## Design principles - -Marked tends to favor following the SOLID set of software design and development principles; mainly the [single responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle) and [open/closed principles](https://en.wikipedia.org/wiki/Open/closed_principle): - -- **Single responsibility:** Marked, and the components of Marked, have the single responsibility of converting Markdown strings into HTML. -- **Open/closed:** Marked favors giving developers the means to easily extend the library and its components over changing Marked's behavior through configuration options. - -## Priorities - -We think we have our priorities sorted to build quality in. - -The following table lists the ticket type labels we use when there is work to be done on the code either through an Issue or a PR; in priority order. - -|Ticket type label |Description | -|:----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -|L0 - security |A security vulnerability within the Marked library is discovered. | -|L1 - broken |Valid usage results in incorrect output compared to [supported specifications](#/README.md#specifications) OR causes marked to crash AND there is no known workaround for the issue. | -|L2 - annoying |Similar to L1 - broken only there is a known workaround available for the issue. | -|RR - refactor and re-engineer |Results in an improvement to developers using Marked (improved readability) or end-users (faster performance) or both. | -|NFS - new feature (spec related) |A capability Marked does not currently provide but is in one of the [supported specifications](#/README.md#specifications) | -|NFU - new feature (user requested) |A capability Marked does not currently provide but has been requested by users of Marked. | -|NFE - new feature (should be an extension) |A capability Marked does not currently provide and is not part of a spec. | - -## Test early, often, and everything - -We try to write test cases to validate output (writing tests based on the [supported specifications](#/README.md#specifications)) and minimize regression (writing tests for issues fixed). Therefore, if you would like to contribute, some things you should know regarding the test harness. - -|Location |Description | -|:---------------------|:--------------------------------------------------------------------------------------------------------------| -|/test/specs/commonmark|Tests for [CommonMark](https://spec.commonmark.org/current/) compliance | -|/test/specs/gfm |Tests for [GFM](https://github.github.com/gfm/) compliance | -|/test/specs/new |Tests not related to the original `markdown.pl`. | -|/test/specs/original |Tests validating against the original `markdown.pl`. | -|/test/specs/redos |Tests for [ReDOS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS) vulnerabilities| - -If your test uses features or options, assuming `gfm` is set to `false`, for example, you can add [front-matter](https://www.npmjs.com/package/front-matter) to the top of -your `.md` file - -``` yml ---- -gfm: false ---- -``` - -## Submitting PRs and Issues - -Marked provides templates for submitting both pull requests and issues. When you begin creating a new PR or issue, you will see instructions on using the template. - -The PR templates include checklists for both the submitter and the reviewer, which, in most cases, will not be the same person. - -## Scripts - -When it comes to NPM commands, we try to use the native scripts provided by the NPM framework. - -To run the tests: - -``` bash -npm test -``` - -To test whether you are using the standard syntax rules for the project: - -```bash -npm run test:lint -``` - -To see time comparisons between Marked and other popular Markdown libraries: - -```bash -npm run bench -``` - -To check for (and fix) standardized syntax (lint): - -```bash -npm run lint -``` - -To build your own es5, esm, and minified versions of Marked: - -```bash -npm run build -``` diff --git a/packages/markdown/marked/docs/PUBLISHING.md b/packages/markdown/marked/docs/PUBLISHING.md deleted file mode 100644 index 739726674..000000000 --- a/packages/markdown/marked/docs/PUBLISHING.md +++ /dev/null @@ -1,24 +0,0 @@ -# Releasing Marked - -- [ ] See [contributing](#/CONTRIBUTING.md) -- [ ] Create release branch from `master` (`release-x.y.z`) -- [ ] Submit PR with minimal name: Release x.y.z -- [ ] Complete PR checklists - -## Overall strategy - -**Master is always shippable:** We try to merge PRs in such a way that `master` is the only branch to really be concerned about *and* `master` can always be released. This allows smoother flow between new features, bug fixes, and so on. (Almost a continuous deployment setup, without automation.) - -## Versioning - -We follow [semantic versioning](https://semver.org) where the following sequence is true `[major].[minor].[patch]`; therefore, consider the following implications of the release you are preparing: - -1. **Major:** There is at least one change not deemed backward compatible. -2. **Minor:** There is at least one new feature added to the release. -3. **Patch:** No breaking changes, no new features. - -What to expect while Marked is a zero-major (0.x.y): - -1. The major will remain at zero; thereby, alerting consumers to the potentially volatile nature of the package. -2. The minor will tend to be more analogous to a `major` release. -3. The patch will tend to be more analogous to a `minor` release or a collection of bug fixes (patches). diff --git a/packages/markdown/marked/docs/README.md b/packages/markdown/marked/docs/README.md deleted file mode 100644 index f5ba9d2b7..000000000 --- a/packages/markdown/marked/docs/README.md +++ /dev/null @@ -1,85 +0,0 @@ -Marked is - -1. built for speed.* -2. a low-level markdown compiler for parsing markdown without caching or blocking for long periods of time.** -3. light-weight while implementing all markdown features from the supported flavors & specifications.*** -4. available as a command line interface (CLI) and running in client- or server-side JavaScript projects. - -

* Still working on metrics for comparative analysis and definition.
-** As few dependencies as possible.
-*** Strict compliance could result in slower processing when running comparative benchmarking.

- - -

Demo

- -Checkout the [demo page](./demo/) to see marked in action ⛹️ - -These documentation pages are also rendered using marked 💯 - - -

Installation

- -**CLI:** `npm install -g marked` - -**In-browser:** `npm install marked` - -

Usage

- -### Warning: 🚨 Marked does not [sanitize](https://marked.js.org/#/USING_ADVANCED.md#options) the output HTML. Please use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! 🚨 - -**CLI** - -``` bash -$ marked -o hello.html -hello world -^D -$ cat hello.html -

hello world

-``` - -``` bash -$ marked -s "*hello world*" -

hello world

-``` - -**Browser** - -```html - - - - - Marked in the browser - - -
- - - - -``` - - -Marked offers [advanced configurations](#/USING_ADVANCED.md) and [extensibility](#/USING_PRO.md) as well. - -

Supported Markdown specifications

- -We actively support the features of the following [Markdown flavors](https://github.com/commonmark/CommonMark/wiki/Markdown-Flavors). - -|Flavor |Version | -|:----------------------------------------------------------|:----------| -|The original markdown.pl |-- | -|[CommonMark](http://spec.commonmark.org/0.29/) |0.29 | -|[GitHub Flavored Markdown](https://github.github.com/gfm/) |0.29 | - -By supporting the above Markdown flavors, it's possible that Marked can help you use other flavors as well; however, these are not actively supported by the community. - -

Security

- -The only completely secure system is the one that doesn't exist in the first place. Having said that, we take the security of Marked very seriously. - -Therefore, please disclose potential security issues by email to the project [committers](#/AUTHORS.md) as well as the [listed owners within NPM](https://docs.npmjs.com/cli/owner). We will provide an initial assessment of security reports within 48 hours and should apply patches within 2 weeks (also, feel free to contribute a fix for the issue). - diff --git a/packages/markdown/marked/docs/USING_ADVANCED.md b/packages/markdown/marked/docs/USING_ADVANCED.md deleted file mode 100644 index 81df205b9..000000000 --- a/packages/markdown/marked/docs/USING_ADVANCED.md +++ /dev/null @@ -1,152 +0,0 @@ -## The `marked` function - -```js -marked(markdownString [,options] [,callback]) -``` - -|Argument |Type |Notes | -|:---------------------|:------------|:----------------------------------------------------------------------------------------------------| -|markdownString |`string` |String of markdown source to be compiled. | -|options|`object`|Hash of options. Can also use `marked.setOptions`. | -|callback |`function` |Called when `markdownString` has been parsed. Can be used as second argument if no `options` present.| - -### Alternative using reference - -```js -// Create reference instance -const marked = require('marked'); - -// Set options -// `highlight` example uses `highlight.js` -marked.setOptions({ - renderer: new marked.Renderer(), - highlight: function(code, language) { - const hljs = require('highlight.js'); - const validLanguage = hljs.getLanguage(language) ? language : 'plaintext'; - return hljs.highlight(validLanguage, code).value; - }, - pedantic: false, - gfm: true, - breaks: false, - sanitize: false, - smartLists: true, - smartypants: false, - xhtml: false -}); - -// Compile -console.log(marked(markdownString)); -``` - -

Options

- -|Member |Type |Default |Since |Notes | -|:-----------|:---------|:--------|:--------|:-------------| -|baseUrl |`string` |`null` |0.3.9 |A prefix url for any relative link. | -|breaks |`boolean` |`false` |v0.2.7 |If true, add `
` on a single line break (copies GitHub). Requires `gfm` be `true`.| -|gfm |`boolean` |`true` |v0.2.1 |If true, use approved [GitHub Flavored Markdown (GFM) specification](https://github.github.com/gfm/).| -|headerIds |`boolean` |`true` |v0.4.0 |If true, include an `id` attribute when emitting headings (h1, h2, h3, etc).| -|headerPrefix|`string` |`''` |v0.3.0 |A string to prefix the `id` attribute when emitting headings (h1, h2, h3, etc).| -|highlight |`function`|`null` |v0.3.0 |A function to highlight code blocks, see Asynchronous highlighting.| -|langPrefix |`string` |`'language-'`|v0.3.0|A string to prefix the className in a `` block. Useful for syntax highlighting.| -|mangle |`boolean` |`true` |v0.3.4 |If true, autolinked email address is escaped with HTML character references.| -|pedantic |`boolean` |`false` |v0.2.1 |If true, conform to the original `markdown.pl` as much as possible. Don't fix original markdown bugs or behavior. Turns off and overrides `gfm`.| -|renderer |`object` |`new Renderer()`|v0.3.0|An object containing functions to render tokens to HTML. See [extensibility](/#/USING_PRO.md) for more details.| -|sanitize |`boolean` |`false` |v0.2.1 |If true, sanitize the HTML passed into `markdownString` with the `sanitizer` function.
**Warning**: This feature is deprecated and it should NOT be used as it cannot be considered secure.
Instead use a sanitize library, like [DOMPurify](https://github.com/cure53/DOMPurify) (recommended), [sanitize-html](https://github.com/apostrophecms/sanitize-html) or [insane](https://github.com/bevacqua/insane) on the output HTML! | -|sanitizer |`function`|`null` |v0.3.4 |A function to sanitize the HTML passed into `markdownString`.| -|silent |`boolean` |`false` |v0.2.7 |If true, the parser does not throw any exception.| -|smartLists |`boolean` |`false` |v0.2.8 |If true, use smarter list behavior than those found in `markdown.pl`.| -|smartypants |`boolean` |`false` |v0.2.9 |If true, use "smart" typographic punctuation for things like quotes and dashes.| -|xhtml |`boolean` |`false` |v0.3.2 |If true, emit self-closing HTML tags for void elements (<br/>, <img/>, etc.) with a "/" as required by XHTML.| - -

Asynchronous highlighting

- -Unlike `highlight.js` the `pygmentize.js` library uses asynchronous highlighting. This example demonstrates that marked is agnostic when it comes to the highlighter you use. - -```js -marked.setOptions({ - highlight: function(code, lang, callback) { - require('pygmentize-bundled') ({ lang: lang, format: 'html' }, code, function (err, result) { - callback(err, result.toString()); - }); - } -}); - -console.log(marked(markdownString)); -``` - -In both examples, `code` is a `string` representing the section of code to pass to the highlighter. In this example, `lang` is a `string` informing the highlighter what programming language to use for the `code` and `callback` is the `function` the asynchronous highlighter will call once complete. - -

Workers

- -To prevent ReDoS attacks you can run marked on a worker and terminate it when parsing takes longer than usual. - -Marked can be run in a [worker thread](https://nodejs.org/api/worker_threads.html) on a node server, or a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) in a browser. - -### Node Worker Thread - -```js -// markedWorker.js - -const marked = require('marked'); -const { parentPort } = require('worker_threads'); - -parentPort.on('message', (markdownString) => { - parentPort.postMessage(marked(markdownString)); -}); -``` - -```js -// index.js - -const { Worker } = require('worker_threads'); -const markedWorker = new Worker('./markedWorker.js'); - -const markedTimeout = setTimeout(() => { - markedWorker.terminate(); - throw new Error('Marked took too long!'); -}, timeoutLimit); - -markedWorker.on('message', (html) => { - clearTimeout(markedTimeout); - console.log(html); - markedWorker.terminate(); -}); - -markedWorker.postMessage(markdownString); -``` - -### Web Worker - -> **NOTE**: Web Workers send the payload from `postMessage` in an object with the payload in a `.data` property - -```js -// markedWorker.js - -importScripts('path/to/marked.min.js'); - -onmessage = (e) => { - const markdownString = e.data - postMessage(marked(markdownString)); -}; -``` - -```js -// script.js - -const markedWorker = new Worker('./markedWorker.js'); - -const markedTimeout = setTimeout(() => { - markedWorker.terminate(); - throw new Error('Marked took too long!'); -}, timeoutLimit); - -markedWorker.onmessage = (e) => { - clearTimeout(markedTimeout); - const html = e.data; - console.log(html); - markedWorker.terminate(); -}; - -markedWorker.postMessage(markdownString); -``` diff --git a/packages/markdown/marked/docs/USING_PRO.md b/packages/markdown/marked/docs/USING_PRO.md deleted file mode 100644 index 5e9451bee..000000000 --- a/packages/markdown/marked/docs/USING_PRO.md +++ /dev/null @@ -1,163 +0,0 @@ -## Extending Marked - -To champion the single-responsibility and open/closed principles, we have tried to make it relatively painless to extend marked. If you are looking to add custom functionality, this is the place to start. - -

The renderer

- -The renderer is... - -**Example:** Overriding default heading token by adding an embedded anchor tag like on GitHub. - -```js -// Create reference instance -const marked = require('marked'); - -// Get reference -const renderer = new marked.Renderer(); - -// Override function -renderer.heading = function (text, level) { - const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-'); - - return ` - - - - - ${text} - `; -}; - -// Run marked -console.log(marked('# heading+', { renderer: renderer })); -``` - -**Output:** - -```html -

- - - - heading+ -

-``` - -### Block level renderer methods - -- code(*string* code, *string* infostring, *boolean* escaped) -- blockquote(*string* quote) -- html(*string* html) -- heading(*string* text, *number* level, *string* raw, *Slugger* slugger) -- hr() -- list(*string* body, *boolean* ordered, *number* start) -- listitem(*string* text, *boolean* task, *boolean* checked) -- checkbox(*boolean* checked) -- paragraph(*string* text) -- table(*string* header, *string* body) -- tablerow(*string* content) -- tablecell(*string* content, *object* flags) - -`slugger` has the `slug` method to create an unique id from value: - -```js -slugger.slug('foo') // foo -slugger.slug('foo') // foo-1 -slugger.slug('foo') // foo-2 -slugger.slug('foo 1') // foo-1-1 -slugger.slug('foo-1') // foo-1-2 -... -``` - -`flags` has the following properties: - -```js -{ - header: true || false, - align: 'center' || 'left' || 'right' -} -``` - -### Inline level renderer methods - -- strong(*string* text) -- em(*string* text) -- codespan(*string* code) -- br() -- del(*string* text) -- link(*string* href, *string* title, *string* text) -- image(*string* href, *string* title, *string* text) -- text(*string* text) - -

The lexer

- -The lexer is... - - -

The parser

- -The parser is... - -*** - -

Access to lexer and parser

- -You also have direct access to the lexer and parser if you so desire. - -``` js -const tokens = marked.lexer(text, options); -console.log(marked.parser(tokens, options)); -``` - -``` js -const lexer = new marked.Lexer(options); -const tokens = lexer.lex(text); -console.log(tokens); -console.log(lexer.rules); -``` - -``` bash -$ node -> require('marked').lexer('> i am using marked.') -[ { type: 'blockquote_start' }, - { type: 'paragraph', - text: 'i am using marked.' }, - { type: 'blockquote_end' }, - links: {} ] -``` - -The Lexers build an array of tokens, which will be passed to their respective -Parsers. The Parsers process each token in the token arrays, -which are removed from the array of tokens: - -``` js -const marked = require('marked'); - -const md = ` - # heading - - [link][1] - - [1]: #heading "heading" -`; - -const tokens = marked.lexer(md); -console.log(tokens); - -const html = marked.parser(tokens); -console.log(html); - -console.log(tokens); -``` - -``` bash -[ { type: 'heading', depth: 1, text: 'heading' }, - { type: 'paragraph', text: ' [link][1]' }, - { type: 'space' }, - links: { '1': { href: '#heading', title: 'heading' } } ] - -

heading

-

link

- -[ links: { '1': { href: '#heading', title: 'heading' } } ] -``` diff --git a/packages/markdown/marked/docs/broken.md b/packages/markdown/marked/docs/broken.md deleted file mode 100644 index 7bfa49e8a..000000000 --- a/packages/markdown/marked/docs/broken.md +++ /dev/null @@ -1,426 +0,0 @@ -# Markdown is broken - -I have a lot of scraps of markdown engine oddities that I've collected over the -years. What you see below is slightly messy, but it's what I've managed to -cobble together to illustrate the differences between markdown engines, and -why, if there ever is a markdown specification, it has to be absolutely -thorough. There are a lot more of these little differences I have documented -elsewhere. I know I will find them lingering on my disk one day, but until -then, I'll continue to add whatever strange nonsensical things I find. - -Some of these examples may only mention a particular engine compared to marked. -However, the examples with markdown.pl could easily be swapped out for -discount, upskirt, or markdown.js, and you would very easily see even more -inconsistencies. - -A lot of this was written when I was very unsatisfied with the inconsistencies -between markdown engines. Please excuse the frustration noticeable in my -writing. - -## Examples of markdown's "stupid" list parsing - -``` -$ markdown.pl - - * item1 - - * item2 - - text -^D -

-``` - - -``` -$ marked - * item1 - - * item2 - - text -^D - -``` - -Which looks correct to you? - -- - - - -``` -$ markdown.pl -* hello - > world -^D -

- -``` - -``` -$ marked -* hello - > world -^D - -``` - -Again, which looks correct to you? - -- - - - -EXAMPLE: - -``` -$ markdown.pl -* hello - * world - * hi - code -^D - -``` - -The code isn't a code block even though it's after the bullet margin. I know, -lets give it two more spaces, effectively making it 8 spaces past the bullet. - -``` -$ markdown.pl -* hello - * world - * hi - code -^D - -``` - -And, it's still not a code block. Did you also notice that the 3rd item isn't -even its own list? Markdown screws that up too because of its indentation -unaware parsing. - -- - - - -Let's look at some more examples of markdown's list parsing: - -``` -$ markdown.pl - - * item1 - - * item2 - - text -^D -

-``` - -Misnested tags. - - -``` -$ marked - * item1 - - * item2 - - text -^D - -``` - -Which looks correct to you? - -- - - - -``` -$ markdown.pl -* hello - > world -^D -

- -``` - -More misnested tags. - - -``` -$ marked -* hello - > world -^D - -``` - -Again, which looks correct to you? - -- - - - -# Why quality matters - Part 2 - -``` bash -$ markdown.pl -* hello - > world -^D -

- -``` - -``` bash -$ sundown # upskirt -* hello - > world -^D - -``` - -``` bash -$ marked -* hello - > world -^D - -``` - -Which looks correct to you? - -- - - - -See: https://github.com/evilstreak/markdown-js/issues/23 - -``` bash -$ markdown.pl # upskirt/markdown.js/discount -* hello - var a = 1; -* world -^D - -``` - -``` bash -$ marked -* hello - var a = 1; -* world -^D -
-``` - -Which looks more reasonable? Why shouldn't code blocks be able to appear in -list items in a sane way? - -- - - - -``` bash -$ markdown.js -
hello
- -hello -^D -

<div>hello</div>

- -

<span>hello</span>

-``` - -``` bash -$ marked -
hello
- -hello -^D -
hello
- - -

hello -

-``` - -- - - - -See: https://github.com/evilstreak/markdown-js/issues/27 - -``` bash -$ markdown.js -[![an image](/image)](/link) -^D -

![an image

-``` - -``` bash -$ marked -[![an image](/image)](/link) -^D -

an image -

-``` - -- - - - -See: https://github.com/evilstreak/markdown-js/issues/24 - -``` bash -$ markdown.js -> a - -> b - -> c -^D -

a

bundefined> c

-``` - -``` bash -$ marked -> a - -> b - -> c -^D -

a - -

-

b - -

-

c -

-``` - -- - - - -``` bash -$ markdown.pl -* hello - * world - how - - are - you - - * today -* hi -^D - -``` - -``` bash -$ marked -* hello - * world - how - - are - you - - * today -* hi -^D - -``` diff --git a/packages/markdown/marked/docs/demo/demo.css b/packages/markdown/marked/docs/demo/demo.css deleted file mode 100644 index 398c663a5..000000000 --- a/packages/markdown/marked/docs/demo/demo.css +++ /dev/null @@ -1,72 +0,0 @@ -html, body { - margin: 0; - padding: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #333; - background-color: #fbfbfb; - height: 100%; -} - -textarea { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 12px; - resize: none; -} - -header { - padding-top: 10px; - display: flex; - height: 58px; -} - -header h1 { - margin: 0; -} - -.github-ribbon { - position: absolute; - top: 0; - right: 0; - border: 0; - z-index: 1000; -} - -.containers { - display: flex; - height: calc(100vh - 68px); -} - -.container { - flex-basis: 50%; - padding: 5px; - display: flex; - flex-direction: column; - height: 100%; - box-sizing: border-box; -} - -.pane, .inputPane { - margin-top: 5px; - padding: 0.6em; - border: 1px solid #ccc; - overflow: auto; - flex-grow: 1; - flex-shrink: 1; -} - -#preview { - display: flex; -} - -#preview iframe { - flex-grow: 1; -} - -#main { - display: none; -} - -.error { - border-color: red; - background-color: #FEE -} diff --git a/packages/markdown/marked/docs/demo/demo.js b/packages/markdown/marked/docs/demo/demo.js deleted file mode 100644 index 056495830..000000000 --- a/packages/markdown/marked/docs/demo/demo.js +++ /dev/null @@ -1,534 +0,0 @@ -/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare - -if (!window.Promise) { - window.Promise = ES6Promise; -} -if (!window.fetch) { - window.fetch = unfetch; -} - -onunhandledrejection = function(e) { - throw e.reason; -}; - -var $loadingElem = document.querySelector('#loading'); -var $mainElem = document.querySelector('#main'); -var $markdownElem = document.querySelector('#markdown'); -var $markedVerElem = document.querySelector('#markedVersion'); -var $commitVerElem = document.querySelector('#commitVersion'); -var $markedVer = document.querySelector('#markedCdn'); -var $optionsElem = document.querySelector('#options'); -var $outputTypeElem = document.querySelector('#outputType'); -var $inputTypeElem = document.querySelector('#inputType'); -var $responseTimeElem = document.querySelector('#responseTime'); -var $previewElem = document.querySelector('#preview'); -var $previewIframe = document.querySelector('#preview iframe'); -var $permalinkElem = document.querySelector('#permalink'); -var $clearElem = document.querySelector('#clear'); -var $htmlElem = document.querySelector('#html'); -var $lexerElem = document.querySelector('#lexer'); -var $panes = document.querySelectorAll('.pane'); -var $inputPanes = document.querySelectorAll('.inputPane'); -var lastInput = ''; -var inputDirty = true; -var $activeOutputElem = null; -var search = searchToObject(); -var markedVersions = { - master: 'https://cdn.jsdelivr.net/gh/markedjs/marked/lib/marked.js' -}; -var markedVersionCache = {}; -var delayTime = 1; -var checkChangeTimeout = null; -var markedWorker; - -$previewIframe.addEventListener('load', handleIframeLoad); - -$outputTypeElem.addEventListener('change', handleOutputChange, false); - -$inputTypeElem.addEventListener('change', handleInputChange, false); - -$markedVerElem.addEventListener('change', handleVersionChange, false); - -$markdownElem.addEventListener('change', handleInput, false); -$markdownElem.addEventListener('keyup', handleInput, false); -$markdownElem.addEventListener('keypress', handleInput, false); -$markdownElem.addEventListener('keydown', handleInput, false); - -$optionsElem.addEventListener('change', handleInput, false); -$optionsElem.addEventListener('keyup', handleInput, false); -$optionsElem.addEventListener('keypress', handleInput, false); -$optionsElem.addEventListener('keydown', handleInput, false); - -$commitVerElem.style.display = 'none'; -$commitVerElem.addEventListener('keypress', handleAddVersion, false); - -$clearElem.addEventListener('click', handleClearClick, false); - -Promise.all([ - setInitialQuickref(), - setInitialOutputType(), - setInitialText(), - setInitialVersion() - .then(setInitialOptions) -]).then(function() { - handleInputChange(); - handleOutputChange(); - checkForChanges(); - setScrollPercent(0); - $loadingElem.style.display = 'none'; - $mainElem.style.display = 'block'; -}); - -function setInitialText() { - if ('text' in search) { - $markdownElem.value = search.text; - } else { - return fetch('./initial.md') - .then(function(res) { return res.text(); }) - .then(function(text) { - if ($markdownElem.value === '') { - $markdownElem.value = text; - } - }); - } -} - -function setInitialQuickref() { - return fetch('./quickref.md') - .then(function(res) { return res.text(); }) - .then(function(text) { - document.querySelector('#quickref').value = text; - }); -} - -function setInitialVersion() { - return fetch('https://data.jsdelivr.com/v1/package/npm/marked') - .then(function(res) { - return res.json(); - }) - .then(function(json) { - for (var i = 0; i < json.versions.length; i++) { - var ver = json.versions[i]; - markedVersions[ver] = 'https://cdn.jsdelivr.net/npm/marked@' + ver + '/lib/marked.js'; - var opt = document.createElement('option'); - opt.textContent = ver; - opt.value = ver; - $markedVerElem.appendChild(opt); - } - }) - .then(function() { - return fetch('https://api.github.com/repos/markedjs/marked/commits') - .then(function(res) { - return res.json(); - }) - .then(function(json) { - markedVersions.master = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + json[0].sha + '/lib/marked.js'; - }) - .catch(function() { - // do nothing - // uses url without commit - }); - }) - .then(function() { - if (search.version) { - if (markedVersions[search.version]) { - return search.version; - } else { - var match = search.version.match(/^(\w+):(.+)$/); - if (match) { - switch (match[1]) { - case 'commit': - addCommitVersion(search.version, match[2].substring(0, 7), match[2]); - return search.version; - case 'pr': - return getPrCommit(match[2]) - .then(function(commit) { - if (!commit) { - return 'master'; - } - addCommitVersion(search.version, 'PR #' + match[2], commit); - return search.version; - }); - } - } - } - } - - return 'master'; - }) - .then(function(version) { - $markedVerElem.value = version; - }) - .then(updateVersion); -} - -function setInitialOptions() { - if ('options' in search) { - $optionsElem.value = search.options; - } else { - setDefaultOptions(); - } -} - -function setInitialOutputType() { - if (search.outputType) { - $outputTypeElem.value = search.outputType; - } -} - -function handleIframeLoad() { - lastInput = ''; - inputDirty = true; -} - -function handleInput() { - inputDirty = true; -}; - -function handleVersionChange() { - if ($markedVerElem.value === 'commit' || $markedVerElem.value === 'pr') { - $commitVerElem.style.display = ''; - } else { - $commitVerElem.style.display = 'none'; - updateVersion(); - } -} - -function handleClearClick() { - $markdownElem.value = ''; - $markedVerElem.value = 'master'; - $commitVerElem.style.display = 'none'; - updateVersion().then(setDefaultOptions); -} - -function handleAddVersion(e) { - if (e.which === 13) { - switch ($markedVerElem.value) { - case 'commit': - var commit = $commitVerElem.value.toLowerCase(); - if (!commit.match(/^[0-9a-f]{40}$/)) { - alert('That is not a valid commit'); - return; - } - addCommitVersion('commit:' + commit, commit.substring(0, 7), commit); - $markedVerElem.value = 'commit:' + commit; - $commitVerElem.style.display = 'none'; - $commitVerElem.value = ''; - updateVersion(); - break; - case 'pr': - $commitVerElem.disabled = true; - var pr = $commitVerElem.value.replace(/\D/g, ''); - getPrCommit(pr) - .then(function(commit) { - $commitVerElem.disabled = false; - if (!commit) { - alert('That is not a valid PR'); - return; - } - addCommitVersion('pr:' + pr, 'PR #' + pr, commit); - $markedVerElem.value = 'pr:' + pr; - $commitVerElem.style.display = 'none'; - $commitVerElem.value = ''; - updateVersion(); - }); - } - } -} - -function handleInputChange() { - handleChange($inputPanes, $inputTypeElem.value); -} - -function handleOutputChange() { - $activeOutputElem = handleChange($panes, $outputTypeElem.value); - updateLink(); -} - -function handleChange(panes, visiblePane) { - var active = null; - for (var i = 0; i < panes.length; i++) { - if (panes[i].id === visiblePane) { - panes[i].style.display = ''; - active = panes[i]; - } else { - panes[i].style.display = 'none'; - } - } - return active; -}; - -function addCommitVersion(value, text, commit) { - if (markedVersions[value]) { - return; - } - markedVersions[value] = 'https://cdn.jsdelivr.net/gh/markedjs/marked@' + commit + '/lib/marked.js'; - var opt = document.createElement('option'); - opt.textContent = text; - opt.value = value; - $markedVerElem.insertBefore(opt, $markedVerElem.firstChild); -} - -function getPrCommit(pr) { - return fetch('https://api.github.com/repos/markedjs/marked/pulls/' + pr + '/commits') - .then(function(res) { - return res.json(); - }) - .then(function(json) { - return json[json.length - 1].sha; - }).catch(function() { - // return undefined - }); -} - -function setDefaultOptions() { - if (window.Worker) { - messageWorker({ - task: 'defaults', - version: markedVersions[$markedVerElem.value] - }); - } else { - var defaults = marked.getDefaults(); - setOptions(defaults); - } -} - -function setOptions(opts) { - $optionsElem.value = JSON.stringify( - opts, - function(key, value) { - if (value && typeof value === 'object' && Object.getPrototypeOf(value) !== Object.prototype) { - return undefined; - } - return value; - }, ' '); -} - -function searchToObject() { - // modified from https://stackoverflow.com/a/7090123/806777 - var pairs = location.search.slice(1).split('&'); - var obj = {}; - - for (var i = 0; i < pairs.length; i++) { - if (pairs[i] === '') { - continue; - } - - var pair = pairs[i].split('='); - - obj[decodeURIComponent(pair.shift())] = decodeURIComponent(pair.join('=')); - } - - return obj; -} - -function jsonString(input) { - var output = (input + '') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\t/g, '\\t') - .replace(/\f/g, '\\f') - .replace(/[\\"']/g, '\\$&') - .replace(/\u0000/g, '\\0'); - return '"' + output + '"'; -}; - -function getScrollSize() { - var e = $activeOutputElem; - - return e.scrollHeight - e.clientHeight; -}; - -function getScrollPercent() { - var size = getScrollSize(); - - if (size <= 0) { - return 1; - } - - return $activeOutputElem.scrollTop / size; -}; - -function setScrollPercent(percent) { - $activeOutputElem.scrollTop = percent * getScrollSize(); -}; - -function updateLink() { - var outputType = ''; - if ($outputTypeElem.value !== 'preview') { - outputType = 'outputType=' + $outputTypeElem.value + '&'; - } - - $permalinkElem.href = '?' + outputType + 'text=' + encodeURIComponent($markdownElem.value) - + '&options=' + encodeURIComponent($optionsElem.value) - + '&version=' + encodeURIComponent($markedVerElem.value); - history.replaceState('', document.title, $permalinkElem.href); -} - -function updateVersion() { - if (window.Worker) { - handleInput(); - return Promise.resolve(); - } - var promise; - if (markedVersionCache[$markedVerElem.value]) { - promise = Promise.resolve(markedVersionCache[$markedVerElem.value]); - } else { - promise = fetch(markedVersions[$markedVerElem.value]) - .then(function(res) { return res.text(); }) - .then(function(text) { - markedVersionCache[$markedVerElem.value] = text; - return text; - }); - } - return promise.then(function(text) { - var script = document.createElement('script'); - script.textContent = text; - - $markedVer.parentNode.replaceChild(script, $markedVer); - $markedVer = script; - }).then(handleInput); -} - -function checkForChanges() { - if (inputDirty && $markedVerElem.value !== 'commit' && $markedVerElem.value !== 'pr' && (typeof marked !== 'undefined' || window.Worker)) { - inputDirty = false; - - updateLink(); - - var options = {}; - var optionsString = $optionsElem.value || '{}'; - try { - var newOptions = JSON.parse(optionsString); - options = newOptions; - $optionsElem.classList.remove('error'); - } catch (err) { - $optionsElem.classList.add('error'); - } - - var version = markedVersions[$markedVerElem.value]; - var markdown = $markdownElem.value; - var hash = version + markdown + optionsString; - if (lastInput !== hash) { - lastInput = hash; - if (window.Worker) { - delayTime = 100; - messageWorker({ - task: 'parse', - version: version, - markdown: markdown, - options: options - }); - } else { - var startTime = new Date(); - var lexed = marked.lexer(markdown, options); - var lexedList = []; - for (var i = 0; i < lexed.length; i++) { - var lexedLine = []; - for (var j in lexed[i]) { - lexedLine.push(j + ':' + jsonString(lexed[i][j])); - } - lexedList.push('{' + lexedLine.join(', ') + '}'); - } - var parsed = marked.parser(lexed, options); - var scrollPercent = getScrollPercent(); - setParsed(parsed, lexedList.join('\n')); - setScrollPercent(scrollPercent); - var endTime = new Date(); - delayTime = endTime - startTime; - setResponseTime(delayTime); - if (delayTime < 50) { - delayTime = 50; - } else if (delayTime > 500) { - delayTime = 1000; - } - } - } - } - checkChangeTimeout = window.setTimeout(checkForChanges, delayTime); -}; - -function setResponseTime(ms) { - var amount = ms; - var suffix = 'ms'; - if (ms > 1000 * 60 * 60) { - amount = 'Too Long'; - suffix = ''; - } else if (ms > 1000 * 60) { - amount = '>' + Math.floor(ms / (1000 * 60)); - suffix = 'm'; - } else if (ms > 1000) { - amount = '>' + Math.floor(ms / 1000); - suffix = 's'; - } - $responseTimeElem.textContent = amount + suffix; -} - -function setParsed(parsed, lexed) { - try { - $previewIframe.contentDocument.body.innerHTML = parsed; - } catch (ex) {} - $htmlElem.value = parsed; - $lexerElem.value = lexed; -} - -function messageWorker(message) { - if (!markedWorker || markedWorker.working) { - if (markedWorker) { - clearTimeout(markedWorker.timeout); - markedWorker.terminate(); - } - markedWorker = new Worker('worker.js'); - markedWorker.onmessage = function(e) { - clearTimeout(markedWorker.timeout); - markedWorker.working = false; - switch (e.data.task) { - case 'defaults': - setOptions(e.data.defaults); - break; - case 'parse': - $previewElem.classList.remove('error'); - $htmlElem.classList.remove('error'); - $lexerElem.classList.remove('error'); - var scrollPercent = getScrollPercent(); - setParsed(e.data.parsed, e.data.lexed); - setScrollPercent(scrollPercent); - setResponseTime(e.data.time); - break; - } - clearTimeout(checkChangeTimeout); - delayTime = 10; - checkForChanges(); - }; - markedWorker.onerror = markedWorker.onmessageerror = function(err) { - clearTimeout(markedWorker.timeout); - var error = 'There was an error in the Worker'; - if (err) { - if (err.message) { - error = err.message; - } else { - error = err; - } - } - error = error.replace(/^Uncaught Error: /, ''); - $previewElem.classList.add('error'); - $htmlElem.classList.add('error'); - $lexerElem.classList.add('error'); - setParsed(error, error); - setScrollPercent(0); - }; - } - if (message.task !== 'defaults') { - markedWorker.working = true; - workerTimeout(0); - } - markedWorker.postMessage(message); -} - -function workerTimeout(seconds) { - markedWorker.timeout = setTimeout(function() { - seconds++; - markedWorker.onerror('Marked has taken longer than ' + seconds + ' second' + (seconds > 1 ? 's' : '') + ' to respond...'); - workerTimeout(seconds); - }, 1000); -} diff --git a/packages/markdown/marked/docs/demo/index.html b/packages/markdown/marked/docs/demo/index.html deleted file mode 100644 index 96a8ec14b..000000000 --- a/packages/markdown/marked/docs/demo/index.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - Marked Demo - - - - - - Fork me on GitHub - - -
- - - -

Marked Demo

-
- -
Loading...
-
-
-
-
- Input · - · - Version: - - · - - -
- - -
- -
-
- · - Response Time: - -
- -
- - -
- - - - - - -
-
-
- - - - - - - diff --git a/packages/markdown/marked/docs/demo/initial.md b/packages/markdown/marked/docs/demo/initial.md deleted file mode 100644 index d2b7d77c1..000000000 --- a/packages/markdown/marked/docs/demo/initial.md +++ /dev/null @@ -1,36 +0,0 @@ -Marked - Markdown Parser -======================== - -[Marked] lets you convert [Markdown] into HTML. Markdown is a simple text format whose goal is to be very easy to read and write, even when not converted to HTML. This demo page will let you type anything you like and see how it gets converted. Live. No more waiting around. - -How To Use The Demo -------------------- - -1. Type in stuff on the left. -2. See the live updates on the right. - -That's it. Pretty simple. There's also a drop-down option in the upper right to switch between various views: - -- **Preview:** A live display of the generated HTML as it would render in a browser. -- **HTML Source:** The generated HTML before your browser makes it pretty. -- **Lexer Data:** What [marked] uses internally, in case you like gory stuff like this. -- **Quick Reference:** A brief run-down of how to format things using markdown. - -Why Markdown? -------------- - -It's easy. It's not overly bloated, unlike HTML. Also, as the creator of [markdown] says, - -> The overriding design goal for Markdown's -> formatting syntax is to make it as readable -> as possible. The idea is that a -> Markdown-formatted document should be -> publishable as-is, as plain text, without -> looking like it's been marked up with tags -> or formatting instructions. - -Ready to start writing? Either start changing stuff on the left or -[clear everything](/demo/?text=) with a simple click. - -[Marked]: https://github.com/markedjs/marked/ -[Markdown]: http://daringfireball.net/projects/markdown/ diff --git a/packages/markdown/marked/docs/demo/preview.html b/packages/markdown/marked/docs/demo/preview.html deleted file mode 100644 index 7e8c89fec..000000000 --- a/packages/markdown/marked/docs/demo/preview.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - marked.js preview - - - - - - diff --git a/packages/markdown/marked/docs/demo/quickref.md b/packages/markdown/marked/docs/demo/quickref.md deleted file mode 100644 index 10f09bdac..000000000 --- a/packages/markdown/marked/docs/demo/quickref.md +++ /dev/null @@ -1,167 +0,0 @@ -Markdown Quick Reference -======================== - -This guide is a very brief overview, with examples, of the syntax that [Markdown] supports. It is itself written in Markdown and you can copy the samples over to the left-hand pane for experimentation. It's shown as *text* and not *rendered HTML*. - -[Markdown]: http://daringfireball.net/projects/markdown/ - - -Simple Text Formatting -====================== - -First thing is first. You can use *stars* or _underscores_ for italics. **Double stars** and __double underscores__ do bold. ***Three together*** do ___both___. - -Paragraphs are pretty easy too. Just have a blank line between chunks of text. - -> This chunk of text is in a block quote. Its multiple lines will all be -> indended a bit from the rest of the text. -> -> > Multiple levels of block quotes also work. - -Sometimes you want to include some code, such as when you are explaining how `

` HTML tags work, or maybe you are a programmer and you are discussing `someMethod()`. - -If you want to include some code and have -newlines preserved, indent the line with a tab -or at least four spaces. - Extra spaces work here too. -This is also called preformatted text and it is useful for showing examples. -The text will stay as text, so any *markdown* or HTML you add will -not show up formatted. This way you can show markdown examples in a -markdown document. - -> You can also use preformatted text with your blockquotes -> as long as you add at least five spaces. - - -Headings -======== - -There are a couple of ways to make headings. Using three or more equals signs on a line under a heading makes it into an "h1" style. Three or more hyphens under a line makes it "h2" (slightly smaller). You can also use multiple pound symbols before and after a heading. Pounds after the title are ignored. Here's some examples: - -This is H1 -========== - -This is H2 ----------- - -# This is H1 -## This is H2 -### This is H3 with some extra pounds ### -#### You get the idea #### -##### I don't need extra pounds at the end -###### H6 is the max - - -Links -===== - -Let's link to a few sites. First, let's use the bare URL, like . Great for text, but ugly for HTML. -Next is an inline link to [Google](http://www.google.com). A little nicer. -This is a reference-style link to [Wikipedia] [1]. -Lastly, here's a pretty link to [Yahoo]. The reference-style and pretty links both automatically use the links defined below, but they could be defined *anywhere* in the markdown and are removed from the HTML. The names are also case insensitive, so you can use [YaHoO] and have it link properly. - -[1]: http://www.wikipedia.org/ -[Yahoo]: http://www.yahoo.com/ - -Title attributes may be added to links by adding text after a link. -This is the [inline link](http://www.bing.com "Bing") with a "Bing" title. -You can also go to [W3C] [2] and maybe visit a [friend]. - -[2]: http://w3c.org (The W3C puts out specs for web-based things) -[Friend]: http://facebook.com/ "Facebook!" - -Email addresses in plain text are not linked: test@example.com. -Email addresses wrapped in angle brackets are linked: . -They are also obfuscated so that email harvesting spam robots hopefully won't get them. - - -Lists -===== - -* This is a bulleted list -* Great for shopping lists -- You can also use hyphens -+ Or plus symbols - -The above is an "unordered" list. Now, on for a bit of order. - -1. Numbered lists are also easy -2. Just start with a number -3738762. However, the actual number doesn't matter when converted to HTML. -1. This will still show up as 4. - -You might want a few advanced lists: - -- This top-level list is wrapped in paragraph tags -- This generates an extra space between each top-level item. - -- You do it by adding a blank line - -- This nested list also has blank lines between the list items. - -- How to create nested lists -1. Start your regular list -2. Indent nested lists with four spaces -3. Further nesting means you should indent with four more spaces - * This line is indented with eight spaces. - -- List items can be quite lengthy. You can keep typing and either continue -them on the next line with no indentation. - -- Alternately, if that looks ugly, you can also -indent the next line a bit for a prettier look. - -- You can put large blocks of text in your list by just indenting with four spaces. - -This is formatted the same as code, but you can inspect the HTML -and find that it's just wrapped in a `

` tag and *won't* be shown -as preformatted text. - -You can keep adding more and more paragraphs to a single -list item by adding the traditional blank line and then keep -on indenting the paragraphs with four spaces. You really need -to only indent the first line, but that looks ugly. - -- Lists support blockquotes - -> Just like this example here. By the way, you can -> nest lists inside blockquotes! -> - Fantastic! - -- Lists support preformatted text - - You just need to indent eight spaces. - - -Even More -========= - -Horizontal Rule ---------------- - -If you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters. - ---- -**************************** -_ _ _ _ _ _ _ - -Those three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens. - -Images ------- - -Images work exactly like links, but they have exclamation points in front. They work with references and titles too. - -![Google Logo](http://www.google.com/images/errors/logo_sm.gif) and ![Happy]. - -[Happy]: http://www.wpclipart.com/smiley/simple_smiley/smiley_face_simple_green_small.png ("Smiley face") - - -Inline HTML ------------ - -If markdown is too limiting, you can just insert your own crazy HTML. Span-level HTML can *still* use markdown. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML. - -

-It is a pity, but markdown does **not** work in here for most markdown parsers. [Marked] handles it pretty well. -
diff --git a/packages/markdown/marked/docs/demo/worker.js b/packages/markdown/marked/docs/demo/worker.js deleted file mode 100644 index 6a0fa0323..000000000 --- a/packages/markdown/marked/docs/demo/worker.js +++ /dev/null @@ -1,105 +0,0 @@ -/* globals marked, unfetch, ES6Promise, Promise */ // eslint-disable-line no-redeclare -if (!self.Promise) { - self.importScripts('https://cdn.jsdelivr.net/npm/es6-promise/dist/es6-promise.js'); - self.Promise = ES6Promise; -} -if (!self.fetch) { - self.importScripts('https://cdn.jsdelivr.net/npm/unfetch/dist/unfetch.umd.js'); - self.fetch = unfetch; -} - -var versionCache = {}; -var currentVersion; - -onunhandledrejection = function(e) { - throw e.reason; -}; - -onmessage = function(e) { - if (e.data.version === currentVersion) { - parse(e); - } else { - loadVersion(e.data.version).then(function() { - parse(e); - }); - } -}; - -function parse(e) { - switch (e.data.task) { - case 'defaults': - - var defaults = {}; - if (typeof marked.getDefaults === 'function') { - defaults = marked.getDefaults(); - delete defaults.renderer; - } else if ('defaults' in marked) { - for (var prop in marked.defaults) { - if (prop !== 'renderer') { - defaults[prop] = marked.defaults[prop]; - } - } - } - postMessage({ - task: e.data.task, - defaults: defaults - }); - break; - case 'parse': - var startTime = new Date(); - var lexed = marked.lexer(e.data.markdown, e.data.options); - var lexedList = []; - for (var i = 0; i < lexed.length; i++) { - var lexedLine = []; - for (var j in lexed[i]) { - lexedLine.push(j + ':' + jsonString(lexed[i][j])); - } - lexedList.push('{' + lexedLine.join(', ') + '}'); - } - var parsed = marked.parser(lexed, e.data.options); - var endTime = new Date(); - // setTimeout(function () { - postMessage({ - task: e.data.task, - lexed: lexedList.join('\n'), - parsed: parsed, - time: endTime - startTime - }); - // }, 10000); - break; - } -} - -function jsonString(input) { - var output = (input + '') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\t/g, '\\t') - .replace(/\f/g, '\\f') - .replace(/[\\"']/g, '\\$&') - .replace(/\u0000/g, '\\0'); - return '"' + output + '"'; -}; - -function loadVersion(ver) { - var promise; - if (versionCache[ver]) { - promise = Promise.resolve(versionCache[ver]); - } else { - promise = fetch(ver) - .then(function(res) { return res.text(); }) - .then(function(text) { - versionCache[ver] = text; - return text; - }); - } - return promise.then(function(text) { - try { - // eslint-disable-next-line no-new-func - Function(text)(); - } catch (err) { - throw new Error('Cannot load that version of marked'); - } - currentVersion = ver; - }); -} diff --git a/packages/markdown/marked/docs/img/logo-black-and-white.svg b/packages/markdown/marked/docs/img/logo-black-and-white.svg deleted file mode 100644 index 5f6c0b781..000000000 --- a/packages/markdown/marked/docs/img/logo-black-and-white.svg +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/markdown/marked/docs/img/logo-black.svg b/packages/markdown/marked/docs/img/logo-black.svg deleted file mode 100644 index a67fb80e5..000000000 --- a/packages/markdown/marked/docs/img/logo-black.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/markdown/marked/docs/index.html b/packages/markdown/marked/docs/index.html deleted file mode 100644 index 6aed62796..000000000 --- a/packages/markdown/marked/docs/index.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - Marked.js Documentation - - - - - - -
-
- - - -

Marked.js Documentation

-
- -
- - - - - - diff --git a/packages/markdown/marked/lib/marked.esm.js b/packages/markdown/marked/lib/marked.esm.js deleted file mode 100644 index 8a42d3786..000000000 --- a/packages/markdown/marked/lib/marked.esm.js +++ /dev/null @@ -1,1830 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2020, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -/** - * DO NOT EDIT THIS FILE - * The code in this file is generated from files in ./src/ - */ - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -var defaults = createCommonjsModule(function (module) { -function getDefaults() { - return { - baseUrl: null, - breaks: false, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: null, - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - xhtml: false - }; -} - -function changeDefaults(newDefaults) { - module.exports.defaults = newDefaults; -} - -module.exports = { - defaults: getDefaults(), - getDefaults, - changeDefaults -}; -}); -var defaults_1 = defaults.defaults; -var defaults_2 = defaults.getDefaults; -var defaults_3 = defaults.changeDefaults; - -/** - * Helpers - */ -const escapeTest = /[&<>"']/; -const escapeReplace = /[&<>"']/g; -const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; -const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; -const escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; -const getEscapeReplacement = (ch) => escapeReplacements[ch]; -function escape(html, encode) { - if (encode) { - if (escapeTest.test(html)) { - return html.replace(escapeReplace, getEscapeReplacement); - } - } else { - if (escapeTestNoEncode.test(html)) { - return html.replace(escapeReplaceNoEncode, getEscapeReplacement); - } - } - - return html; -} - -const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; - -function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(unescapeTest, (_, n) => { - n = n.toLowerCase(); - if (n === 'colon') return ':'; - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' - ? String.fromCharCode(parseInt(n.substring(2), 16)) - : String.fromCharCode(+n.substring(1)); - } - return ''; - }); -} - -const caret = /(^|[^\[])\^/g; -function edit(regex, opt) { - regex = regex.source || regex; - opt = opt || ''; - const obj = { - replace: (name, val) => { - val = val.source || val; - val = val.replace(caret, '$1'); - regex = regex.replace(name, val); - return obj; - }, - getRegex: () => { - return new RegExp(regex, opt); - } - }; - return obj; -} - -const nonWordAndColonTest = /[^\w:]/g; -const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; -function cleanUrl(sanitize, base, href) { - if (sanitize) { - let prot; - try { - prot = decodeURIComponent(unescape(href)) - .replace(nonWordAndColonTest, '') - .toLowerCase(); - } catch (e) { - return null; - } - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return null; - } - } - if (base && !originIndependentUrl.test(href)) { - href = resolveUrl(base, href); - } - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return null; - } - return href; -} - -const baseUrls = {}; -const justDomain = /^[^:]+:\/*[^/]*$/; -const protocol = /^([^:]+:)[\s\S]*$/; -const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/; - -function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (justDomain.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = rtrim(base, '/', true); - } - } - base = baseUrls[' ' + base]; - const relativeBase = base.indexOf(':') === -1; - - if (href.substring(0, 2) === '//') { - if (relativeBase) { - return href; - } - return base.replace(protocol, '$1') + href; - } else if (href.charAt(0) === '/') { - if (relativeBase) { - return href; - } - return base.replace(domain, '$1') + href; - } else { - return base + href; - } -} - -const noopTest = { exec: function noopTest() {} }; - -function merge(obj) { - let i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments[i]; - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; -} - -function splitCells(tableRow, count) { - // ensure that every cell-delimiting pipe has a space - // before it to distinguish it from an escaped pipe - const row = tableRow.replace(/\|/g, (match, offset, str) => { - let escaped = false, - curr = offset; - while (--curr >= 0 && str[curr] === '\\') escaped = !escaped; - if (escaped) { - // odd number of slashes means | is escaped - // so we leave it alone - return '|'; - } else { - // add space before unescaped | - return ' |'; - } - }), - cells = row.split(/ \|/); - let i = 0; - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) cells.push(''); - } - - for (; i < cells.length; i++) { - // leading or trailing whitespace is ignored per the gfm spec - cells[i] = cells[i].trim().replace(/\\\|/g, '|'); - } - return cells; -} - -// Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). -// /c*$/ is vulnerable to REDOS. -// invert: Remove suffix of non-c chars instead. Default falsey. -function rtrim(str, c, invert) { - const l = str.length; - if (l === 0) { - return ''; - } - - // Length of suffix matching the invert condition. - let suffLen = 0; - - // Step left until we fail to match the invert condition. - while (suffLen < l) { - const currChar = str.charAt(l - suffLen - 1); - if (currChar === c && !invert) { - suffLen++; - } else if (currChar !== c && invert) { - suffLen++; - } else { - break; - } - } - - return str.substr(0, l - suffLen); -} - -function findClosingBracket(str, b) { - if (str.indexOf(b[1]) === -1) { - return -1; - } - const l = str.length; - let level = 0, - i = 0; - for (; i < l; i++) { - if (str[i] === '\\') { - i++; - } else if (str[i] === b[0]) { - level++; - } else if (str[i] === b[1]) { - level--; - if (level < 0) { - return i; - } - } - } - return -1; -} - -function checkSanitizeDeprecation(opt) { - if (opt && opt.sanitize && !opt.silent) { - console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); - } -} - -var helpers = { - escape, - unescape, - edit, - cleanUrl, - resolveUrl, - noopTest, - merge, - splitCells, - rtrim, - findClosingBracket, - checkSanitizeDeprecation -}; - -const { - noopTest: noopTest$1, - edit: edit$1, - merge: merge$1 -} = helpers; - -/** - * Block-Level Grammar - */ -const block = { - newline: /^\n+/, - code: /^( {4}[^\n]+\n*)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) - + '|\\n*' // (4) - + '|\\n*' // (5) - + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - nptable: noopTest$1, - table: noopTest$1, - lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/, - // regex template, placeholders will be replaced according to different paragraph - // interruption rules of commonmark and the original markdown spec: - _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/, - text: /^[^\n]+/ -}; - -block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; -block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; -block.def = edit$1(block.def) - .replace('label', block._label) - .replace('title', block._title) - .getRegex(); - -block.bullet = /(?:[*+-]|\d{1,9}\.)/; -block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; -block.item = edit$1(block.item, 'gm') - .replace(/bull/g, block.bullet) - .getRegex(); - -block.list = edit$1(block.list) - .replace(/bull/g, block.bullet) - .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') - .replace('def', '\\n+(?=' + block.def.source + ')') - .getRegex(); - -block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' - + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' - + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' - + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' - + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' - + '|track|ul'; -block._comment = //; -block.html = edit$1(block.html, 'i') - .replace('comment', block._comment) - .replace('tag', block._tag) - .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) - .getRegex(); - -block.paragraph = edit$1(block._paragraph) - .replace('hr', block.hr) - .replace('heading', ' {0,3}#{1,6} ') - .replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs - .replace('blockquote', ' {0,3}>') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)') - .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - -block.blockquote = edit$1(block.blockquote) - .replace('paragraph', block.paragraph) - .getRegex(); - -/** - * Normal Block Grammar - */ - -block.normal = merge$1({}, block); - -/** - * GFM Block Grammar - */ - -block.gfm = merge$1({}, block.normal, { - nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, - table: '^ *\\|(.+)\\n' // Header - + ' *\\|?( *[-:]+[-| :]*)' // Align - + '(?:\\n((?:(?!^|>|\\n| |hr|heading|lheading|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells -}); - -block.gfm.table = edit$1(block.gfm.table) - .replace('hr', block.hr) - .replace('heading', ' {0,3}#{1,6} ') - .replace('lheading', '([^\\n]+)\\n {0,3}(=+|-+) *(?:\\n+|$)') - .replace('blockquote', ' {0,3}>') - .replace('code', ' {4}[^\\n]') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)') - .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - -/** - * Pedantic grammar (original John Gruber's loose markdown specification) - */ - -block.pedantic = merge$1({}, block.normal, { - html: edit$1( - '^ *(?:comment *(?:\\n|\\s*$)' - + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag - + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') - .replace('comment', block._comment) - .replace(/tag/g, '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' - + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' - + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') - .getRegex(), - def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, - heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, - fences: noopTest$1, // fences not supported - paragraph: edit$1(block.normal._paragraph) - .replace('hr', block.hr) - .replace('heading', ' *#{1,6} *[^\n]') - .replace('lheading', block.lheading) - .replace('blockquote', ' {0,3}>') - .replace('|fences', '') - .replace('|list', '') - .replace('|html', '') - .getRegex() -}); - -/** - * Inline-Level Grammar - */ -const inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noopTest$1, - tag: '^comment' - + '|^' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. - + '|^' // declaration, e.g. - + '|^', // CDATA section - link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, - nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - strong: /^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, - em: /^_([^\s_])_(?!_)|^\*([^\s*<\[])\*(?!\*)|^_([^\s<][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_<][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s<"][\s\S]*?[^\s\*])\*(?!\*|[^\spunctuation])|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, - code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, - br: /^( {2,}|\\)\n(?!\s*$)/, - del: noopTest$1, - text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\?@\\[^_{|}~'; -inline.em = edit$1(inline.em).replace(/punctuation/g, inline._punctuation).getRegex(); - -inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - -inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; -inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; -inline.autolink = edit$1(inline.autolink) - .replace('scheme', inline._scheme) - .replace('email', inline._email) - .getRegex(); - -inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - -inline.tag = edit$1(inline.tag) - .replace('comment', block._comment) - .replace('attribute', inline._attribute) - .getRegex(); - -inline._label = /(?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; -inline._href = /<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/; -inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - -inline.link = edit$1(inline.link) - .replace('label', inline._label) - .replace('href', inline._href) - .replace('title', inline._title) - .getRegex(); - -inline.reflink = edit$1(inline.reflink) - .replace('label', inline._label) - .getRegex(); - -/** - * Normal Inline Grammar - */ - -inline.normal = merge$1({}, inline); - -/** - * Pedantic Inline Grammar - */ - -inline.pedantic = merge$1({}, inline.normal, { - strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - link: edit$1(/^!?\[(label)\]\((.*?)\)/) - .replace('label', inline._label) - .getRegex(), - reflink: edit$1(/^!?\[(label)\]\s*\[([^\]]*)\]/) - .replace('label', inline._label) - .getRegex() -}); - -/** - * GFM Inline Grammar - */ - -inline.gfm = merge$1({}, inline.normal, { - escape: edit$1(inline.escape).replace('])', '~|])').getRegex(), - _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, - url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^~+(?=\S)([\s\S]*?\S)~+/, - text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\ 1) { - this.tokens.push({ - type: 'space' - }); - } - } - - // code - if (cap = this.rules.code.exec(src)) { - const lastToken = this.tokens[this.tokens.length - 1]; - src = src.substring(cap[0].length); - // An indented code block cannot interrupt a paragraph. - if (lastToken && lastToken.type === 'paragraph') { - lastToken.text += '\n' + cap[0].trimRight(); - } else { - cap = cap[0].replace(/^ {4}/gm, ''); - this.tokens.push({ - type: 'code', - codeBlockStyle: 'indented', - text: !this.options.pedantic - ? rtrim$1(cap, '\n') - : cap - }); - } - continue; - } - - // fences - if (cap = this.rules.fences.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'code', - lang: cap[2] ? cap[2].trim() : cap[2], - text: cap[3] || '' - }); - continue; - } - - // heading - if (cap = this.rules.heading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[1].length, - text: cap[2] - }); - continue; - } - - // table no leading pipe (gfm) - if (cap = this.rules.nptable.exec(src)) { - item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells$1(item.cells[i], item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // hr - if (cap = this.rules.hr.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'hr' - }); - continue; - } - - // blockquote - if (cap = this.rules.blockquote.exec(src)) { - src = src.substring(cap[0].length); - - this.tokens.push({ - type: 'blockquote_start' - }); - - cap = cap[0].replace(/^ *> ?/gm, ''); - - // Pass `top` to keep the current - // "toplevel" state. This is exactly - // how markdown.pl works. - this.token(cap, top); - - this.tokens.push({ - type: 'blockquote_end' - }); - - continue; - } - - // list - if (cap = this.rules.list.exec(src)) { - src = src.substring(cap[0].length); - bull = cap[2]; - isordered = bull.length > 1; - - listStart = { - type: 'list_start', - ordered: isordered, - start: isordered ? +bull : '', - loose: false - }; - - this.tokens.push(listStart); - - // Get each top-level item. - cap = cap[0].match(this.rules.item); - - listItems = []; - next = false; - l = cap.length; - i = 0; - - for (; i < l; i++) { - item = cap[i]; - - // Remove the list item's bullet - // so it is seen as the next token. - space = item.length; - item = item.replace(/^ *([*+-]|\d+\.) */, ''); - - // Outdent whatever the - // list item contains. Hacky. - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic - ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') - : item.replace(/^ {1,4}/gm, ''); - } - - // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - if (i !== l - 1) { - b = block$1.bullet.exec(cap[i + 1])[0]; - if (bull.length > 1 ? b.length === 1 - : (b.length > 1 || (this.options.smartLists && b !== bull))) { - src = cap.slice(i + 1).join('\n') + src; - i = l - 1; - } - } - - // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - loose = next || /\n\n(?!\s*$)/.test(item); - if (i !== l - 1) { - next = item.charAt(item.length - 1) === '\n'; - if (!loose) loose = next; - } - - if (loose) { - listStart.loose = true; - } - - // Check for task list items - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); - } - - t = { - type: 'list_item_start', - task: istask, - checked: ischecked, - loose: loose - }; - - listItems.push(t); - this.tokens.push(t); - - // Recurse. - this.token(item, false); - - this.tokens.push({ - type: 'list_item_end' - }); - } - - if (listStart.loose) { - l = listItems.length; - i = 0; - for (; i < l; i++) { - listItems[i].loose = true; - } - } - - this.tokens.push({ - type: 'list_end' - }); - - continue; - } - - // html - if (cap = this.rules.html.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: this.options.sanitize - ? 'paragraph' - : 'html', - pre: !this.options.sanitizer - && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$1(cap[0])) : cap[0] - }); - continue; - } - - // def - if (top && (cap = this.rules.def.exec(src))) { - src = src.substring(cap[0].length); - if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - if (!this.tokens.links[tag]) { - this.tokens.links[tag] = { - href: cap[2], - title: cap[3] - }; - } - continue; - } - - // table (gfm) - if (cap = this.rules.table.exec(src)) { - item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells$1( - item.cells[i].replace(/^ *\| *| *\| *$/g, ''), - item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // lheading - if (cap = this.rules.lheading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1] - }); - continue; - } - - // top-level paragraph - if (top && (cap = this.rules.paragraph.exec(src))) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'paragraph', - text: cap[1].charAt(cap[1].length - 1) === '\n' - ? cap[1].slice(0, -1) - : cap[1] - }); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - // Top-level should never reach here. - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'text', - text: cap[0] - }); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return this.tokens; - }; -}; - -const { defaults: defaults$2 } = defaults; -const { - cleanUrl: cleanUrl$1, - escape: escape$2 -} = helpers; - -/** - * Renderer - */ -var Renderer_1 = class Renderer { - constructor(options) { - this.options = options || defaults$2; - } - - code(code, infostring, escaped) { - const lang = (infostring || '').match(/\S*/)[0]; - if (this.options.highlight) { - const out = this.options.highlight(code, lang); - if (out != null && out !== code) { - escaped = true; - code = out; - } - } - - if (!lang) { - return '
'
-        + (escaped ? code : escape$2(code, true))
-        + '
'; - } - - return '
'
-      + (escaped ? code : escape$2(code, true))
-      + '
\n'; - }; - - blockquote(quote) { - return '
\n' + quote + '
\n'; - }; - - html(html) { - return html; - }; - - heading(text, level, raw, slugger) { - if (this.options.headerIds) { - return '' - + text - + '\n'; - } - // ignore IDs - return '' + text + '\n'; - }; - - hr() { - return this.options.xhtml ? '
\n' : '
\n'; - }; - - list(body, ordered, start) { - const type = ordered ? 'ol' : 'ul', - startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - }; - - listitem(text) { - return '
  • ' + text + '
  • \n'; - }; - - checkbox(checked) { - return ' '; - }; - - paragraph(text) { - return '

    ' + text + '

    \n'; - }; - - table(header, body) { - if (body) body = '' + body + ''; - - return '\n' - + '\n' - + header - + '\n' - + body - + '
    \n'; - }; - - tablerow(content) { - return '\n' + content + '\n'; - }; - - tablecell(content, flags) { - const type = flags.header ? 'th' : 'td'; - const tag = flags.align - ? '<' + type + ' align="' + flags.align + '">' - : '<' + type + '>'; - return tag + content + '\n'; - }; - - // span level renderer - strong(text) { - return '' + text + ''; - }; - - em(text) { - return '' + text + ''; - }; - - codespan(text) { - return '' + text + ''; - }; - - br() { - return this.options.xhtml ? '
    ' : '
    '; - }; - - del(text) { - return '' + text + ''; - }; - - link(href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - if (href === null) { - return text; - } - let out = ''; - return out; - }; - - image(href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - if (href === null) { - return text; - } - - let out = '' + text + '' : '>'; - return out; - }; - - text(text) { - return text; - }; -}; - -/** - * Slugger generates header id - */ -var Slugger_1 = class Slugger { - constructor() { - this.seen = {}; - } - - /** - * Convert string to unique id - */ - slug(value) { - let slug = value - .toLowerCase() - .trim() - .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '') - .replace(/\s/g, '-'); - - if (this.seen.hasOwnProperty(slug)) { - const originalSlug = slug; - do { - this.seen[originalSlug]++; - slug = originalSlug + '-' + this.seen[originalSlug]; - } while (this.seen.hasOwnProperty(slug)); - } - this.seen[slug] = 0; - - return slug; - }; -}; - -const { defaults: defaults$3 } = defaults; -const { inline: inline$1 } = rules; -const { - findClosingBracket: findClosingBracket$1, - escape: escape$3 -} = helpers; - -/** - * Inline Lexer & Compiler - */ -var InlineLexer_1 = class InlineLexer { - constructor(links, options) { - this.options = options || defaults$3; - this.links = links; - this.rules = inline$1.normal; - this.options.renderer = this.options.renderer || new Renderer_1(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - - if (!this.links) { - throw new Error('Tokens array requires a `links` property.'); - } - - if (this.options.pedantic) { - this.rules = inline$1.pedantic; - } else if (this.options.gfm) { - if (this.options.breaks) { - this.rules = inline$1.breaks; - } else { - this.rules = inline$1.gfm; - } - } - } - - /** - * Expose Inline Rules - */ - static get rules() { - return inline$1; - } - - /** - * Static Lexing/Compiling Method - */ - static output(src, links, options) { - const inline = new InlineLexer(links, options); - return inline.output(src); - } - - /** - * Lexing/Compiling - */ - output(src) { - let out = '', - link, - text, - href, - title, - cap, - prevCapZero; - - while (src) { - // escape - if (cap = this.rules.escape.exec(src)) { - src = src.substring(cap[0].length); - out += escape$3(cap[1]); - continue; - } - - // tag - if (cap = this.rules.tag.exec(src)) { - if (!this.inLink && /^/i.test(cap[0])) { - this.inLink = false; - } - if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.inRawBlock = true; - } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.inRawBlock = false; - } - - src = src.substring(cap[0].length); - out += this.renderer.html(this.options.sanitize - ? (this.options.sanitizer - ? this.options.sanitizer(cap[0]) - : escape$3(cap[0])) - : cap[0]); - continue; - } - - // link - if (cap = this.rules.link.exec(src)) { - const lastParenIndex = findClosingBracket$1(cap[2], '()'); - if (lastParenIndex > -1) { - const start = cap[0].indexOf('!') === 0 ? 5 : 4; - const linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; - } - src = src.substring(cap[0].length); - this.inLink = true; - href = cap[2]; - if (this.options.pedantic) { - link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } else { - title = ''; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - out += this.outputLink(cap, { - href: InlineLexer.escapes(href), - title: InlineLexer.escapes(title) - }); - this.inLink = false; - continue; - } - - // reflink, nolink - if ((cap = this.rules.reflink.exec(src)) - || (cap = this.rules.nolink.exec(src))) { - src = src.substring(cap[0].length); - link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = this.links[link.toLowerCase()]; - if (!link || !link.href) { - out += cap[0].charAt(0); - src = cap[0].substring(1) + src; - continue; - } - this.inLink = true; - out += this.outputLink(cap, link); - this.inLink = false; - continue; - } - - // strong - if (cap = this.rules.strong.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // em - if (cap = this.rules.em.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // code - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.codespan(escape$3(cap[2].trim(), true)); - continue; - } - - // br - if (cap = this.rules.br.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.br(); - continue; - } - - // del (gfm) - if (cap = this.rules.del.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.del(this.output(cap[1])); - continue; - } - - // autolink - if (cap = this.rules.autolink.exec(src)) { - src = src.substring(cap[0].length); - if (cap[2] === '@') { - text = escape$3(this.mangle(cap[1])); - href = 'mailto:' + text; - } else { - text = escape$3(cap[1]); - href = text; - } - out += this.renderer.link(href, null, text); - continue; - } - - // url (gfm) - if (!this.inLink && (cap = this.rules.url.exec(src))) { - if (cap[2] === '@') { - text = escape$3(cap[0]); - href = 'mailto:' + text; - } else { - // do extended autolink path validation - do { - prevCapZero = cap[0]; - cap[0] = this.rules._backpedal.exec(cap[0])[0]; - } while (prevCapZero !== cap[0]); - text = escape$3(cap[0]); - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - src = src.substring(cap[0].length); - out += this.renderer.link(href, null, text); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - src = src.substring(cap[0].length); - if (this.inRawBlock) { - out += this.renderer.text(this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$3(cap[0])) : cap[0]); - } else { - out += this.renderer.text(escape$3(this.smartypants(cap[0]))); - } - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return out; - } - - static escapes(text) { - return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; - } - - /** - * Compile Link - */ - outputLink(cap, link) { - const href = link.href, - title = link.title ? escape$3(link.title) : null; - - return cap[0].charAt(0) !== '!' - ? this.renderer.link(href, title, this.output(cap[1])) - : this.renderer.image(href, title, escape$3(cap[1])); - } - - /** - * Smartypants Transformations - */ - smartypants(text) { - if (!this.options.smartypants) return text; - return text - // em-dashes - .replace(/---/g, '\u2014') - // en-dashes - .replace(/--/g, '\u2013') - // opening singles - .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') - // closing singles & apostrophes - .replace(/'/g, '\u2019') - // opening doubles - .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') - // closing doubles - .replace(/"/g, '\u201d') - // ellipses - .replace(/\.{3}/g, '\u2026'); - } - - /** - * Mangle Links - */ - mangle(text) { - if (!this.options.mangle) return text; - const l = text.length; - let out = '', - i = 0, - ch; - - for (; i < l; i++) { - ch = text.charCodeAt(i); - if (Math.random() > 0.5) { - ch = 'x' + ch.toString(16); - } - out += '&#' + ch + ';'; - } - - return out; - } -}; - -/** - * TextRenderer - * returns only the textual part of the token - */ -var TextRenderer_1 = class TextRenderer { - // no need for block level renderers - strong(text) { - return text; - } - - em(text) { - return text; - } - - codespan(text) { - return text; - } - - del(text) { - return text; - } - - text(text) { - return text; - } - - link(href, title, text) { - return '' + text; - } - - image(href, title, text) { - return '' + text; - } - - br() { - return ''; - } -}; - -const { defaults: defaults$4 } = defaults; -const { - merge: merge$2, - unescape: unescape$1 -} = helpers; - -/** - * Parsing & Compiling - */ -var Parser_1 = class Parser { - constructor(options) { - this.tokens = []; - this.token = null; - this.options = options || defaults$4; - this.options.renderer = this.options.renderer || new Renderer_1(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - this.slugger = new Slugger_1(); - } - - /** - * Static Parse Method - */ - static parse(tokens, options) { - const parser = new Parser(options); - return parser.parse(tokens); - }; - - /** - * Parse Loop - */ - parse(tokens) { - this.inline = new InlineLexer_1(tokens.links, this.options); - // use an InlineLexer with a TextRenderer to extract pure text - this.inlineText = new InlineLexer_1( - tokens.links, - merge$2({}, this.options, { renderer: new TextRenderer_1() }) - ); - this.tokens = tokens.reverse(); - - let out = ''; - while (this.next()) { - out += this.tok(); - } - - return out; - }; - - /** - * Next Token - */ - next() { - this.token = this.tokens.pop(); - return this.token; - }; - - /** - * Preview Next Token - */ - peek() { - return this.tokens[this.tokens.length - 1] || 0; - }; - - /** - * Parse Text Tokens - */ - parseText() { - let body = this.token.text; - - while (this.peek().type === 'text') { - body += '\n' + this.next().text; - } - - return this.inline.output(body); - }; - - /** - * Parse Current Token - */ - tok() { - let body = ''; - switch (this.token.type) { - case 'space': { - return ''; - } - case 'hr': { - return this.renderer.hr(); - } - case 'heading': { - return this.renderer.heading( - this.inline.output(this.token.text), - this.token.depth, - unescape$1(this.inlineText.output(this.token.text)), - this.slugger); - } - case 'code': { - return this.renderer.code(this.token.text, - this.token.lang, - this.token.escaped); - } - case 'table': { - let header = '', - i, - row, - cell, - j; - - // header - cell = ''; - for (i = 0; i < this.token.header.length; i++) { - cell += this.renderer.tablecell( - this.inline.output(this.token.header[i]), - { header: true, align: this.token.align[i] } - ); - } - header += this.renderer.tablerow(cell); - - for (i = 0; i < this.token.cells.length; i++) { - row = this.token.cells[i]; - - cell = ''; - for (j = 0; j < row.length; j++) { - cell += this.renderer.tablecell( - this.inline.output(row[j]), - { header: false, align: this.token.align[j] } - ); - } - - body += this.renderer.tablerow(cell); - } - return this.renderer.table(header, body); - } - case 'blockquote_start': { - body = ''; - - while (this.next().type !== 'blockquote_end') { - body += this.tok(); - } - - return this.renderer.blockquote(body); - } - case 'list_start': { - body = ''; - const ordered = this.token.ordered, - start = this.token.start; - - while (this.next().type !== 'list_end') { - body += this.tok(); - } - - return this.renderer.list(body, ordered, start); - } - case 'list_item_start': { - body = ''; - const loose = this.token.loose; - const checked = this.token.checked; - const task = this.token.task; - - if (this.token.task) { - if (loose) { - if (this.peek().type === 'text') { - const nextToken = this.peek(); - nextToken.text = this.renderer.checkbox(checked) + ' ' + nextToken.text; - } else { - this.tokens.push({ - type: 'text', - text: this.renderer.checkbox(checked) - }); - } - } else { - body += this.renderer.checkbox(checked); - } - } - - while (this.next().type !== 'list_item_end') { - body += !loose && this.token.type === 'text' - ? this.parseText() - : this.tok(); - } - return this.renderer.listitem(body, task, checked); - } - case 'html': { - // TODO parse inline content if parameter markdown=1 - return this.renderer.html(this.token.text); - } - case 'paragraph': { - return this.renderer.paragraph(this.inline.output(this.token.text)); - } - case 'text': { - return this.renderer.paragraph(this.parseText()); - } - default: { - const errMsg = 'Token with "' + this.token.type + '" type was not found.'; - if (this.options.silent) { - console.log(errMsg); - } else { - throw new Error(errMsg); - } - } - } - }; -}; - -const { - merge: merge$3, - checkSanitizeDeprecation: checkSanitizeDeprecation$1, - escape: escape$4 -} = helpers; -const { - getDefaults, - changeDefaults, - defaults: defaults$5 -} = defaults; - -/** - * Marked - */ -function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected'); - } - - if (callback || typeof opt === 'function') { - if (!callback) { - callback = opt; - opt = null; - } - - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - const highlight = opt.highlight; - let tokens, - pending, - i = 0; - - try { - tokens = Lexer_1.lex(src, opt); - } catch (e) { - return callback(e); - } - - pending = tokens.length; - - const done = function(err) { - if (err) { - opt.highlight = highlight; - return callback(err); - } - - let out; - - try { - out = Parser_1.parse(tokens, opt); - } catch (e) { - err = e; - } - - opt.highlight = highlight; - - return err - ? callback(err) - : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return done(); - } - - delete opt.highlight; - - if (!pending) return done(); - - for (; i < tokens.length; i++) { - (function(token) { - if (token.type !== 'code') { - return --pending || done(); - } - return highlight(token.text, token.lang, function(err, code) { - if (err) return done(err); - if (code == null || code === token.text) { - return --pending || done(); - } - token.text = code; - token.escaped = true; - --pending || done(); - }); - })(tokens[i]); - } - - return; - } - try { - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - return Parser_1.parse(Lexer_1.lex(src, opt), opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if ((opt || marked.defaults).silent) { - return '

    An error occurred:

    '
    -        + escape$4(e.message + '', true)
    -        + '
    '; - } - throw e; - } -} - -/** - * Options - */ - -marked.options = -marked.setOptions = function(opt) { - merge$3(marked.defaults, opt); - changeDefaults(marked.defaults); - return marked; -}; - -marked.getDefaults = getDefaults; - -marked.defaults = defaults$5; - -/** - * Expose - */ - -marked.Parser = Parser_1; -marked.parser = Parser_1.parse; - -marked.Renderer = Renderer_1; -marked.TextRenderer = TextRenderer_1; - -marked.Lexer = Lexer_1; -marked.lexer = Lexer_1.lex; - -marked.InlineLexer = InlineLexer_1; -marked.inlineLexer = InlineLexer_1.output; - -marked.Slugger = Slugger_1; - -marked.parse = marked; - -var marked_1 = marked; - -export default marked_1; diff --git a/packages/markdown/marked/lib/marked.js b/packages/markdown/marked/lib/marked.js deleted file mode 100644 index b40a29da6..000000000 --- a/packages/markdown/marked/lib/marked.js +++ /dev/null @@ -1,1790 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2020, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -/** - * DO NOT EDIT THIS FILE - * The code in this file is generated from files in ./src/ - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = global || self, global.marked = factory()); -}(this, (function () { 'use strict'; - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; - } - - var defaults = createCommonjsModule(function (module) { - function getDefaults() { - return { - baseUrl: null, - breaks: false, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: null, - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - xhtml: false - }; - } - - function changeDefaults(newDefaults) { - module.exports.defaults = newDefaults; - } - - module.exports = { - defaults: getDefaults(), - getDefaults: getDefaults, - changeDefaults: changeDefaults - }; - }); - var defaults_1 = defaults.defaults; - var defaults_2 = defaults.getDefaults; - var defaults_3 = defaults.changeDefaults; - - /** - * Helpers - */ - var escapeTest = /[&<>"']/; - var escapeReplace = /[&<>"']/g; - var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; - var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; - var escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - var getEscapeReplacement = function getEscapeReplacement(ch) { - return escapeReplacements[ch]; - }; - - function escape(html, encode) { - if (encode) { - if (escapeTest.test(html)) { - return html.replace(escapeReplace, getEscapeReplacement); - } - } else { - if (escapeTestNoEncode.test(html)) { - return html.replace(escapeReplaceNoEncode, getEscapeReplacement); - } - } - - return html; - } - - var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; - - function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(unescapeTest, function (_, n) { - n = n.toLowerCase(); - if (n === 'colon') return ':'; - - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1)); - } - - return ''; - }); - } - - var caret = /(^|[^\[])\^/g; - - function edit(regex, opt) { - regex = regex.source || regex; - opt = opt || ''; - var obj = { - replace: function replace(name, val) { - val = val.source || val; - val = val.replace(caret, '$1'); - regex = regex.replace(name, val); - return obj; - }, - getRegex: function getRegex() { - return new RegExp(regex, opt); - } - }; - return obj; - } - - var nonWordAndColonTest = /[^\w:]/g; - var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - - function cleanUrl(sanitize, base, href) { - if (sanitize) { - var prot; - - try { - prot = decodeURIComponent(unescape(href)).replace(nonWordAndColonTest, '').toLowerCase(); - } catch (e) { - return null; - } - - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return null; - } - } - - if (base && !originIndependentUrl.test(href)) { - href = resolveUrl(base, href); - } - - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return null; - } - - return href; - } - - var baseUrls = {}; - var justDomain = /^[^:]+:\/*[^/]*$/; - var protocol = /^([^:]+:)[\s\S]*$/; - var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/; - - function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (justDomain.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = rtrim(base, '/', true); - } - } - - base = baseUrls[' ' + base]; - var relativeBase = base.indexOf(':') === -1; - - if (href.substring(0, 2) === '//') { - if (relativeBase) { - return href; - } - - return base.replace(protocol, '$1') + href; - } else if (href.charAt(0) === '/') { - if (relativeBase) { - return href; - } - - return base.replace(domain, '$1') + href; - } else { - return base + href; - } - } - - var noopTest = { - exec: function noopTest() {} - }; - - function merge(obj) { - var i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments[i]; - - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; - } - - function splitCells(tableRow, count) { - // ensure that every cell-delimiting pipe has a space - // before it to distinguish it from an escaped pipe - var row = tableRow.replace(/\|/g, function (match, offset, str) { - var escaped = false, - curr = offset; - - while (--curr >= 0 && str[curr] === '\\') { - escaped = !escaped; - } - - if (escaped) { - // odd number of slashes means | is escaped - // so we leave it alone - return '|'; - } else { - // add space before unescaped | - return ' |'; - } - }), - cells = row.split(/ \|/); - var i = 0; - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) { - cells.push(''); - } - } - - for (; i < cells.length; i++) { - // leading or trailing whitespace is ignored per the gfm spec - cells[i] = cells[i].trim().replace(/\\\|/g, '|'); - } - - return cells; - } // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). - // /c*$/ is vulnerable to REDOS. - // invert: Remove suffix of non-c chars instead. Default falsey. - - - function rtrim(str, c, invert) { - var l = str.length; - - if (l === 0) { - return ''; - } // Length of suffix matching the invert condition. - - - var suffLen = 0; // Step left until we fail to match the invert condition. - - while (suffLen < l) { - var currChar = str.charAt(l - suffLen - 1); - - if (currChar === c && !invert) { - suffLen++; - } else if (currChar !== c && invert) { - suffLen++; - } else { - break; - } - } - - return str.substr(0, l - suffLen); - } - - function findClosingBracket(str, b) { - if (str.indexOf(b[1]) === -1) { - return -1; - } - - var l = str.length; - var level = 0, - i = 0; - - for (; i < l; i++) { - if (str[i] === '\\') { - i++; - } else if (str[i] === b[0]) { - level++; - } else if (str[i] === b[1]) { - level--; - - if (level < 0) { - return i; - } - } - } - - return -1; - } - - function checkSanitizeDeprecation(opt) { - if (opt && opt.sanitize && !opt.silent) { - console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); - } - } - - var helpers = { - escape: escape, - unescape: unescape, - edit: edit, - cleanUrl: cleanUrl, - resolveUrl: resolveUrl, - noopTest: noopTest, - merge: merge, - splitCells: splitCells, - rtrim: rtrim, - findClosingBracket: findClosingBracket, - checkSanitizeDeprecation: checkSanitizeDeprecation - }; - - var noopTest$1 = helpers.noopTest, - edit$1 = helpers.edit, - merge$1 = helpers.merge; - /** - * Block-Level Grammar - */ - - var block = { - newline: /^\n+/, - code: /^( {4}[^\n]+\n*)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) - + '|\\n*' // (4) - + '|\\n*' // (5) - + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - nptable: noopTest$1, - table: noopTest$1, - lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/, - // regex template, placeholders will be replaced according to different paragraph - // interruption rules of commonmark and the original markdown spec: - _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/, - text: /^[^\n]+/ - }; - block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; - block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; - block.def = edit$1(block.def).replace('label', block._label).replace('title', block._title).getRegex(); - block.bullet = /(?:[*+-]|\d{1,9}\.)/; - block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; - block.item = edit$1(block.item, 'gm').replace(/bull/g, block.bullet).getRegex(); - block.list = edit$1(block.list).replace(/bull/g, block.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block.def.source + ')').getRegex(); - block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul'; - block._comment = //; - block.html = edit$1(block.html, 'i').replace('comment', block._comment).replace('tag', block._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); - block.paragraph = edit$1(block._paragraph).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs - .replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)').replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - block.blockquote = edit$1(block.blockquote).replace('paragraph', block.paragraph).getRegex(); - /** - * Normal Block Grammar - */ - - block.normal = merge$1({}, block); - /** - * GFM Block Grammar - */ - - block.gfm = merge$1({}, block.normal, { - nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, - table: '^ *\\|(.+)\\n' // Header - + ' *\\|?( *[-:]+[-| :]*)' // Align - + '(?:\\n((?:(?!^|>|\\n| |hr|heading|lheading|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells - - }); - block.gfm.table = edit$1(block.gfm.table).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('lheading', '([^\\n]+)\\n {0,3}(=+|-+) *(?:\\n+|$)').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)').replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - /** - * Pedantic grammar (original John Gruber's loose markdown specification) - */ - - block.pedantic = merge$1({}, block.normal, { - html: edit$1('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag - + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))').replace('comment', block._comment).replace(/tag/g, '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b').getRegex(), - def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, - heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, - fences: noopTest$1, - // fences not supported - paragraph: edit$1(block.normal._paragraph).replace('hr', block.hr).replace('heading', ' *#{1,6} *[^\n]').replace('lheading', block.lheading).replace('blockquote', ' {0,3}>').replace('|fences', '').replace('|list', '').replace('|html', '').getRegex() - }); - /** - * Inline-Level Grammar - */ - - var inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noopTest$1, - tag: '^comment' + '|^' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. - + '|^' // declaration, e.g. - + '|^', - // CDATA section - link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, - nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - strong: /^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, - em: /^_([^\s_])_(?!_)|^\*([^\s*<\[])\*(?!\*)|^_([^\s<][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_<][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s<"][\s\S]*?[^\s\*])\*(?!\*|[^\spunctuation])|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, - code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, - br: /^( {2,}|\\)\n(?!\s*$)/, - del: noopTest$1, - text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\?@\\[^_{|}~'; - inline.em = edit$1(inline.em).replace(/punctuation/g, inline._punctuation).getRegex(); - inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; - inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; - inline.autolink = edit$1(inline.autolink).replace('scheme', inline._scheme).replace('email', inline._email).getRegex(); - inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - inline.tag = edit$1(inline.tag).replace('comment', block._comment).replace('attribute', inline._attribute).getRegex(); - inline._label = /(?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; - inline._href = /<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/; - inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - inline.link = edit$1(inline.link).replace('label', inline._label).replace('href', inline._href).replace('title', inline._title).getRegex(); - inline.reflink = edit$1(inline.reflink).replace('label', inline._label).getRegex(); - /** - * Normal Inline Grammar - */ - - inline.normal = merge$1({}, inline); - /** - * Pedantic Inline Grammar - */ - - inline.pedantic = merge$1({}, inline.normal, { - strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - link: edit$1(/^!?\[(label)\]\((.*?)\)/).replace('label', inline._label).getRegex(), - reflink: edit$1(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace('label', inline._label).getRegex() - }); - /** - * GFM Inline Grammar - */ - - inline.gfm = merge$1({}, inline.normal, { - escape: edit$1(inline.escape).replace('])', '~|])').getRegex(), - _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, - url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^~+(?=\S)([\s\S]*?\S)~+/, - text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\ 1) { - this.tokens.push({ - type: 'space' - }); - } - } // code - - - if (cap = this.rules.code.exec(src)) { - var lastToken = this.tokens[this.tokens.length - 1]; - src = src.substring(cap[0].length); // An indented code block cannot interrupt a paragraph. - - if (lastToken && lastToken.type === 'paragraph') { - lastToken.text += '\n' + cap[0].trimRight(); - } else { - cap = cap[0].replace(/^ {4}/gm, ''); - this.tokens.push({ - type: 'code', - codeBlockStyle: 'indented', - text: !this.options.pedantic ? rtrim$1(cap, '\n') : cap - }); - } - - continue; - } // fences - - - if (cap = this.rules.fences.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'code', - lang: cap[2] ? cap[2].trim() : cap[2], - text: cap[3] || '' - }); - continue; - } // heading - - - if (cap = this.rules.heading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[1].length, - text: cap[2] - }); - continue; - } // table no leading pipe (gfm) - - - if (cap = this.rules.nptable.exec(src)) { - item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells$1(item.cells[i], item.header.length); - } - - this.tokens.push(item); - continue; - } - } // hr - - - if (cap = this.rules.hr.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'hr' - }); - continue; - } // blockquote - - - if (cap = this.rules.blockquote.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'blockquote_start' - }); - cap = cap[0].replace(/^ *> ?/gm, ''); // Pass `top` to keep the current - // "toplevel" state. This is exactly - // how markdown.pl works. - - this.token(cap, top); - this.tokens.push({ - type: 'blockquote_end' - }); - continue; - } // list - - - if (cap = this.rules.list.exec(src)) { - src = src.substring(cap[0].length); - bull = cap[2]; - isordered = bull.length > 1; - listStart = { - type: 'list_start', - ordered: isordered, - start: isordered ? +bull : '', - loose: false - }; - this.tokens.push(listStart); // Get each top-level item. - - cap = cap[0].match(this.rules.item); - listItems = []; - next = false; - l = cap.length; - i = 0; - - for (; i < l; i++) { - item = cap[i]; // Remove the list item's bullet - // so it is seen as the next token. - - space = item.length; - item = item.replace(/^ *([*+-]|\d+\.) */, ''); // Outdent whatever the - // list item contains. Hacky. - - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, ''); - } // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - - - if (i !== l - 1) { - b = block$1.bullet.exec(cap[i + 1])[0]; - - if (bull.length > 1 ? b.length === 1 : b.length > 1 || this.options.smartLists && b !== bull) { - src = cap.slice(i + 1).join('\n') + src; - i = l - 1; - } - } // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - - - loose = next || /\n\n(?!\s*$)/.test(item); - - if (i !== l - 1) { - next = item.charAt(item.length - 1) === '\n'; - if (!loose) loose = next; - } - - if (loose) { - listStart.loose = true; - } // Check for task list items - - - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; - - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); - } - - t = { - type: 'list_item_start', - task: istask, - checked: ischecked, - loose: loose - }; - listItems.push(t); - this.tokens.push(t); // Recurse. - - this.token(item, false); - this.tokens.push({ - type: 'list_item_end' - }); - } - - if (listStart.loose) { - l = listItems.length; - i = 0; - - for (; i < l; i++) { - listItems[i].loose = true; - } - } - - this.tokens.push({ - type: 'list_end' - }); - continue; - } // html - - - if (cap = this.rules.html.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: this.options.sanitize ? 'paragraph' : 'html', - pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$1(cap[0]) : cap[0] - }); - continue; - } // def - - - if (top && (cap = this.rules.def.exec(src))) { - src = src.substring(cap[0].length); - if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - - if (!this.tokens.links[tag]) { - this.tokens.links[tag] = { - href: cap[2], - title: cap[3] - }; - } - - continue; - } // table (gfm) - - - if (cap = this.rules.table.exec(src)) { - item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells$1(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length); - } - - this.tokens.push(item); - continue; - } - } // lheading - - - if (cap = this.rules.lheading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1] - }); - continue; - } // top-level paragraph - - - if (top && (cap = this.rules.paragraph.exec(src))) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'paragraph', - text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1] - }); - continue; - } // text - - - if (cap = this.rules.text.exec(src)) { - // Top-level should never reach here. - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'text', - text: cap[0] - }); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return this.tokens; - }; - - _createClass(Lexer, null, [{ - key: "rules", - get: function get() { - return block$1; - } - }]); - - return Lexer; - }(); - - var defaults$2 = defaults.defaults; - var cleanUrl$1 = helpers.cleanUrl, - escape$2 = helpers.escape; - /** - * Renderer - */ - - var Renderer_1 = - /*#__PURE__*/ - function () { - function Renderer(options) { - this.options = options || defaults$2; - } - - var _proto = Renderer.prototype; - - _proto.code = function code(_code, infostring, escaped) { - var lang = (infostring || '').match(/\S*/)[0]; - - if (this.options.highlight) { - var out = this.options.highlight(_code, lang); - - if (out != null && out !== _code) { - escaped = true; - _code = out; - } - } - - if (!lang) { - return '
    ' + (escaped ? _code : escape$2(_code, true)) + '
    '; - } - - return '
    ' + (escaped ? _code : escape$2(_code, true)) + '
    \n'; - }; - - _proto.blockquote = function blockquote(quote) { - return '
    \n' + quote + '
    \n'; - }; - - _proto.html = function html(_html) { - return _html; - }; - - _proto.heading = function heading(text, level, raw, slugger) { - if (this.options.headerIds) { - return '' + text + '\n'; - } // ignore IDs - - - return '' + text + '\n'; - }; - - _proto.hr = function hr() { - return this.options.xhtml ? '
    \n' : '
    \n'; - }; - - _proto.list = function list(body, ordered, start) { - var type = ordered ? 'ol' : 'ul', - startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - }; - - _proto.listitem = function listitem(text) { - return '
  • ' + text + '
  • \n'; - }; - - _proto.checkbox = function checkbox(checked) { - return ' '; - }; - - _proto.paragraph = function paragraph(text) { - return '

    ' + text + '

    \n'; - }; - - _proto.table = function table(header, body) { - if (body) body = '' + body + ''; - return '\n' + '\n' + header + '\n' + body + '
    \n'; - }; - - _proto.tablerow = function tablerow(content) { - return '\n' + content + '\n'; - }; - - _proto.tablecell = function tablecell(content, flags) { - var type = flags.header ? 'th' : 'td'; - var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; - return tag + content + '\n'; - }; - - // span level renderer - _proto.strong = function strong(text) { - return '' + text + ''; - }; - - _proto.em = function em(text) { - return '' + text + ''; - }; - - _proto.codespan = function codespan(text) { - return '' + text + ''; - }; - - _proto.br = function br() { - return this.options.xhtml ? '
    ' : '
    '; - }; - - _proto.del = function del(text) { - return '' + text + ''; - }; - - _proto.link = function link(href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - - if (href === null) { - return text; - } - - var out = '
    '; - return out; - }; - - _proto.image = function image(href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - - if (href === null) { - return text; - } - - var out = '' + text + '' : '>'; - return out; - }; - - _proto.text = function text(_text) { - return _text; - }; - - return Renderer; - }(); - - /** - * Slugger generates header id - */ - var Slugger_1 = - /*#__PURE__*/ - function () { - function Slugger() { - this.seen = {}; - } - /** - * Convert string to unique id - */ - - - var _proto = Slugger.prototype; - - _proto.slug = function slug(value) { - var slug = value.toLowerCase().trim().replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '').replace(/\s/g, '-'); - - if (this.seen.hasOwnProperty(slug)) { - var originalSlug = slug; - - do { - this.seen[originalSlug]++; - slug = originalSlug + '-' + this.seen[originalSlug]; - } while (this.seen.hasOwnProperty(slug)); - } - - this.seen[slug] = 0; - return slug; - }; - - return Slugger; - }(); - - var defaults$3 = defaults.defaults; - var inline$1 = rules.inline; - var findClosingBracket$1 = helpers.findClosingBracket, - escape$3 = helpers.escape; - /** - * Inline Lexer & Compiler - */ - - var InlineLexer_1 = - /*#__PURE__*/ - function () { - function InlineLexer(links, options) { - this.options = options || defaults$3; - this.links = links; - this.rules = inline$1.normal; - this.options.renderer = this.options.renderer || new Renderer_1(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - - if (!this.links) { - throw new Error('Tokens array requires a `links` property.'); - } - - if (this.options.pedantic) { - this.rules = inline$1.pedantic; - } else if (this.options.gfm) { - if (this.options.breaks) { - this.rules = inline$1.breaks; - } else { - this.rules = inline$1.gfm; - } - } - } - /** - * Expose Inline Rules - */ - - - /** - * Static Lexing/Compiling Method - */ - InlineLexer.output = function output(src, links, options) { - var inline = new InlineLexer(links, options); - return inline.output(src); - } - /** - * Lexing/Compiling - */ - ; - - var _proto = InlineLexer.prototype; - - _proto.output = function output(src) { - var out = '', - link, - text, - href, - title, - cap, - prevCapZero; - - while (src) { - // escape - if (cap = this.rules.escape.exec(src)) { - src = src.substring(cap[0].length); - out += escape$3(cap[1]); - continue; - } // tag - - - if (cap = this.rules.tag.exec(src)) { - if (!this.inLink && /^/i.test(cap[0])) { - this.inLink = false; - } - - if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.inRawBlock = true; - } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - this.inRawBlock = false; - } - - src = src.substring(cap[0].length); - out += this.renderer.html(this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$3(cap[0]) : cap[0]); - continue; - } // link - - - if (cap = this.rules.link.exec(src)) { - var lastParenIndex = findClosingBracket$1(cap[2], '()'); - - if (lastParenIndex > -1) { - var start = cap[0].indexOf('!') === 0 ? 5 : 4; - var linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; - } - - src = src.substring(cap[0].length); - this.inLink = true; - href = cap[2]; - - if (this.options.pedantic) { - link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } else { - title = ''; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - out += this.outputLink(cap, { - href: InlineLexer.escapes(href), - title: InlineLexer.escapes(title) - }); - this.inLink = false; - continue; - } // reflink, nolink - - - if ((cap = this.rules.reflink.exec(src)) || (cap = this.rules.nolink.exec(src))) { - src = src.substring(cap[0].length); - link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = this.links[link.toLowerCase()]; - - if (!link || !link.href) { - out += cap[0].charAt(0); - src = cap[0].substring(1) + src; - continue; - } - - this.inLink = true; - out += this.outputLink(cap, link); - this.inLink = false; - continue; - } // strong - - - if (cap = this.rules.strong.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } // em - - - if (cap = this.rules.em.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } // code - - - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.codespan(escape$3(cap[2].trim(), true)); - continue; - } // br - - - if (cap = this.rules.br.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.br(); - continue; - } // del (gfm) - - - if (cap = this.rules.del.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.del(this.output(cap[1])); - continue; - } // autolink - - - if (cap = this.rules.autolink.exec(src)) { - src = src.substring(cap[0].length); - - if (cap[2] === '@') { - text = escape$3(this.mangle(cap[1])); - href = 'mailto:' + text; - } else { - text = escape$3(cap[1]); - href = text; - } - - out += this.renderer.link(href, null, text); - continue; - } // url (gfm) - - - if (!this.inLink && (cap = this.rules.url.exec(src))) { - if (cap[2] === '@') { - text = escape$3(cap[0]); - href = 'mailto:' + text; - } else { - // do extended autolink path validation - do { - prevCapZero = cap[0]; - cap[0] = this.rules._backpedal.exec(cap[0])[0]; - } while (prevCapZero !== cap[0]); - - text = escape$3(cap[0]); - - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - - src = src.substring(cap[0].length); - out += this.renderer.link(href, null, text); - continue; - } // text - - - if (cap = this.rules.text.exec(src)) { - src = src.substring(cap[0].length); - - if (this.inRawBlock) { - out += this.renderer.text(this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$3(cap[0]) : cap[0]); - } else { - out += this.renderer.text(escape$3(this.smartypants(cap[0]))); - } - - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return out; - }; - - InlineLexer.escapes = function escapes(text) { - return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; - } - /** - * Compile Link - */ - ; - - _proto.outputLink = function outputLink(cap, link) { - var href = link.href, - title = link.title ? escape$3(link.title) : null; - return cap[0].charAt(0) !== '!' ? this.renderer.link(href, title, this.output(cap[1])) : this.renderer.image(href, title, escape$3(cap[1])); - } - /** - * Smartypants Transformations - */ - ; - - _proto.smartypants = function smartypants(text) { - if (!this.options.smartypants) return text; - return text // em-dashes - .replace(/---/g, "\u2014") // en-dashes - .replace(/--/g, "\u2013") // opening singles - .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018") // closing singles & apostrophes - .replace(/'/g, "\u2019") // opening doubles - .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201C") // closing doubles - .replace(/"/g, "\u201D") // ellipses - .replace(/\.{3}/g, "\u2026"); - } - /** - * Mangle Links - */ - ; - - _proto.mangle = function mangle(text) { - if (!this.options.mangle) return text; - var l = text.length; - var out = '', - i = 0, - ch; - - for (; i < l; i++) { - ch = text.charCodeAt(i); - - if (Math.random() > 0.5) { - ch = 'x' + ch.toString(16); - } - - out += '&#' + ch + ';'; - } - - return out; - }; - - _createClass(InlineLexer, null, [{ - key: "rules", - get: function get() { - return inline$1; - } - }]); - - return InlineLexer; - }(); - - /** - * TextRenderer - * returns only the textual part of the token - */ - var TextRenderer_1 = - /*#__PURE__*/ - function () { - function TextRenderer() {} - - var _proto = TextRenderer.prototype; - - // no need for block level renderers - _proto.strong = function strong(text) { - return text; - }; - - _proto.em = function em(text) { - return text; - }; - - _proto.codespan = function codespan(text) { - return text; - }; - - _proto.del = function del(text) { - return text; - }; - - _proto.text = function text(_text) { - return _text; - }; - - _proto.link = function link(href, title, text) { - return '' + text; - }; - - _proto.image = function image(href, title, text) { - return '' + text; - }; - - _proto.br = function br() { - return ''; - }; - - return TextRenderer; - }(); - - var defaults$4 = defaults.defaults; - var merge$2 = helpers.merge, - unescape$1 = helpers.unescape; - /** - * Parsing & Compiling - */ - - var Parser_1 = - /*#__PURE__*/ - function () { - function Parser(options) { - this.tokens = []; - this.token = null; - this.options = options || defaults$4; - this.options.renderer = this.options.renderer || new Renderer_1(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - this.slugger = new Slugger_1(); - } - /** - * Static Parse Method - */ - - - Parser.parse = function parse(tokens, options) { - var parser = new Parser(options); - return parser.parse(tokens); - }; - - var _proto = Parser.prototype; - - /** - * Parse Loop - */ - _proto.parse = function parse(tokens) { - this.inline = new InlineLexer_1(tokens.links, this.options); // use an InlineLexer with a TextRenderer to extract pure text - - this.inlineText = new InlineLexer_1(tokens.links, merge$2({}, this.options, { - renderer: new TextRenderer_1() - })); - this.tokens = tokens.reverse(); - var out = ''; - - while (this.next()) { - out += this.tok(); - } - - return out; - }; - - /** - * Next Token - */ - _proto.next = function next() { - this.token = this.tokens.pop(); - return this.token; - }; - - /** - * Preview Next Token - */ - _proto.peek = function peek() { - return this.tokens[this.tokens.length - 1] || 0; - }; - - /** - * Parse Text Tokens - */ - _proto.parseText = function parseText() { - var body = this.token.text; - - while (this.peek().type === 'text') { - body += '\n' + this.next().text; - } - - return this.inline.output(body); - }; - - /** - * Parse Current Token - */ - _proto.tok = function tok() { - var body = ''; - - switch (this.token.type) { - case 'space': - { - return ''; - } - - case 'hr': - { - return this.renderer.hr(); - } - - case 'heading': - { - return this.renderer.heading(this.inline.output(this.token.text), this.token.depth, unescape$1(this.inlineText.output(this.token.text)), this.slugger); - } - - case 'code': - { - return this.renderer.code(this.token.text, this.token.lang, this.token.escaped); - } - - case 'table': - { - var header = '', - i, - row, - cell, - j; // header - - cell = ''; - - for (i = 0; i < this.token.header.length; i++) { - cell += this.renderer.tablecell(this.inline.output(this.token.header[i]), { - header: true, - align: this.token.align[i] - }); - } - - header += this.renderer.tablerow(cell); - - for (i = 0; i < this.token.cells.length; i++) { - row = this.token.cells[i]; - cell = ''; - - for (j = 0; j < row.length; j++) { - cell += this.renderer.tablecell(this.inline.output(row[j]), { - header: false, - align: this.token.align[j] - }); - } - - body += this.renderer.tablerow(cell); - } - - return this.renderer.table(header, body); - } - - case 'blockquote_start': - { - body = ''; - - while (this.next().type !== 'blockquote_end') { - body += this.tok(); - } - - return this.renderer.blockquote(body); - } - - case 'list_start': - { - body = ''; - var ordered = this.token.ordered, - start = this.token.start; - - while (this.next().type !== 'list_end') { - body += this.tok(); - } - - return this.renderer.list(body, ordered, start); - } - - case 'list_item_start': - { - body = ''; - var loose = this.token.loose; - var checked = this.token.checked; - var task = this.token.task; - - if (this.token.task) { - if (loose) { - if (this.peek().type === 'text') { - var nextToken = this.peek(); - nextToken.text = this.renderer.checkbox(checked) + ' ' + nextToken.text; - } else { - this.tokens.push({ - type: 'text', - text: this.renderer.checkbox(checked) - }); - } - } else { - body += this.renderer.checkbox(checked); - } - } - - while (this.next().type !== 'list_item_end') { - body += !loose && this.token.type === 'text' ? this.parseText() : this.tok(); - } - - return this.renderer.listitem(body, task, checked); - } - - case 'html': - { - // TODO parse inline content if parameter markdown=1 - return this.renderer.html(this.token.text); - } - - case 'paragraph': - { - return this.renderer.paragraph(this.inline.output(this.token.text)); - } - - case 'text': - { - return this.renderer.paragraph(this.parseText()); - } - - default: - { - var errMsg = 'Token with "' + this.token.type + '" type was not found.'; - - if (this.options.silent) { - console.log(errMsg); - } else { - throw new Error(errMsg); - } - } - } - }; - - return Parser; - }(); - - var merge$3 = helpers.merge, - checkSanitizeDeprecation$1 = helpers.checkSanitizeDeprecation, - escape$4 = helpers.escape; - var getDefaults = defaults.getDefaults, - changeDefaults = defaults.changeDefaults, - defaults$5 = defaults.defaults; - /** - * Marked - */ - - function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected'); - } - - if (callback || typeof opt === 'function') { - var _ret = function () { - if (!callback) { - callback = opt; - opt = null; - } - - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - var highlight = opt.highlight; - var tokens, - pending, - i = 0; - - try { - tokens = Lexer_1.lex(src, opt); - } catch (e) { - return { - v: callback(e) - }; - } - - pending = tokens.length; - - var done = function done(err) { - if (err) { - opt.highlight = highlight; - return callback(err); - } - - var out; - - try { - out = Parser_1.parse(tokens, opt); - } catch (e) { - err = e; - } - - opt.highlight = highlight; - return err ? callback(err) : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return { - v: done() - }; - } - - delete opt.highlight; - if (!pending) return { - v: done() - }; - - for (; i < tokens.length; i++) { - (function (token) { - if (token.type !== 'code') { - return --pending || done(); - } - - return highlight(token.text, token.lang, function (err, code) { - if (err) return done(err); - - if (code == null || code === token.text) { - return --pending || done(); - } - - token.text = code; - token.escaped = true; - --pending || done(); - }); - })(tokens[i]); - } - - return { - v: void 0 - }; - }(); - - if (typeof _ret === "object") return _ret.v; - } - - try { - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - return Parser_1.parse(Lexer_1.lex(src, opt), opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - - if ((opt || marked.defaults).silent) { - return '

    An error occurred:

    ' + escape$4(e.message + '', true) + '
    '; - } - - throw e; - } - } - /** - * Options - */ - - - marked.options = marked.setOptions = function (opt) { - merge$3(marked.defaults, opt); - changeDefaults(marked.defaults); - return marked; - }; - - marked.getDefaults = getDefaults; - marked.defaults = defaults$5; - /** - * Expose - */ - - marked.Parser = Parser_1; - marked.parser = Parser_1.parse; - marked.Renderer = Renderer_1; - marked.TextRenderer = TextRenderer_1; - marked.Lexer = Lexer_1; - marked.lexer = Lexer_1.lex; - marked.InlineLexer = InlineLexer_1; - marked.inlineLexer = InlineLexer_1.output; - marked.Slugger = Slugger_1; - marked.parse = marked; - var marked_1 = marked; - - return marked_1; - -}))); diff --git a/packages/markdown/marked/man/marked.1 b/packages/markdown/marked/man/marked.1 deleted file mode 100644 index 5cc27ef87..000000000 --- a/packages/markdown/marked/man/marked.1 +++ /dev/null @@ -1,111 +0,0 @@ -.ds q \N'34' -.TH marked 1 - -.SH NAME -marked \- a javascript markdown parser - -.SH SYNOPSIS -.B marked -[\-o \fI\fP] [\-i \fI\fP] [\-\-help] -[\-\-tokens] [\-\-pedantic] [\-\-gfm] -[\-\-breaks] [\-\-sanitize] -[\-\-smart\-lists] [\-\-lang\-prefix \fI\fP] -[\-\-no\-etc...] [\-\-silent] [\fIfilename\fP] - -.SH DESCRIPTION -.B marked -is a full-featured javascript markdown parser, built for speed. -It also includes multiple GFM features. - -.SH EXAMPLES -.TP -cat in.md | marked > out.html -.TP -echo "hello *world*" | marked -.TP -marked \-o out.html \-i in.md \-\-gfm -.TP -marked \-\-output="hello world.html" \-i in.md \-\-no-breaks - -.SH OPTIONS -.TP -.BI \-o,\ \-\-output\ [\fIoutput\fP] -Specify file output. If none is specified, write to stdout. -.TP -.BI \-i,\ \-\-input\ [\fIinput\fP] -Specify file input, otherwise use last argument as input file. -If no input file is specified, read from stdin. -.TP -.BI \-\-test -Makes sure the test(s) pass. -.RS -.PP -.B \-\-glob [\fIfile\fP] -Specify which test to use. -.PP -.B \-\-fix -Fixes tests. -.PP -.B \-\-bench -Benchmarks the test(s). -.PP -.B \-\-time -Times The test(s). -.PP -.B \-\-minified -Runs test file(s) as minified. -.PP -.B \-\-stop -Stop process if a test fails. -.RE -.TP -.BI \-t,\ \-\-tokens -Output a token stream instead of html. -.TP -.BI \-\-pedantic -Conform to obscure parts of markdown.pl as much as possible. -Don't fix original markdown bugs. -.TP -.BI \-\-gfm -Enable github flavored markdown. -.TP -.BI \-\-breaks -Enable GFM line breaks. Only works with the gfm option. -.TP -.BI \-\-sanitize -Sanitize output. Ignore any HTML input. -.TP -.BI \-\-smart\-lists -Use smarter list behavior than the original markdown. -.TP -.BI \-\-lang\-prefix\ [\fIprefix\fP] -Set the prefix for code block classes. -.TP -.BI \-\-mangle -Mangle email addresses. -.TP -.BI \-\-no\-sanitize,\ \-no-etc... -The inverse of any of the marked options above. -.TP -.BI \-\-silent -Silence error output. -.TP -.BI \-h,\ \-\-help -Display help information. - -.SH CONFIGURATION -For configuring and running programmatically. - -.B Example - - require('marked')('*foo*', { gfm: true }); - -.SH BUGS -Please report any bugs to https://github.com/markedjs/marked. - -.SH LICENSE -Copyright (c) 2011-2014, Christopher Jeffrey (MIT License). - -.SH "SEE ALSO" -.BR markdown(1), -.BR node.js(1) diff --git a/packages/markdown/marked/man/marked.1.txt b/packages/markdown/marked/man/marked.1.txt deleted file mode 100644 index 56a8634bc..000000000 --- a/packages/markdown/marked/man/marked.1.txt +++ /dev/null @@ -1,96 +0,0 @@ -marked(1) General Commands Manual marked(1) - -NAME - marked - a javascript markdown parser - -SYNOPSIS - marked [-o ] [-i ] [--help] [--tokens] [--pedantic] - [--gfm] [--breaks] [--sanitize] [--smart-lists] [--lang-prefix ] [--no-etc...] [--silent] [filename] - - -DESCRIPTION - marked is a full-featured javascript markdown parser, built for speed. - It also includes multiple GFM features. - -EXAMPLES - cat in.md | marked > out.html - - echo "hello *world*" | marked - - marked -o out.html -i in.md --gfm - - marked --output="hello world.html" -i in.md --no-breaks - -OPTIONS - -o, --output [output] - Specify file output. If none is specified, write to stdout. - - -i, --input [input] - Specify file input, otherwise use last argument as input file. - If no input file is specified, read from stdin. - - --test Makes sure the test(s) pass. - - --glob [file] Specify which test to use. - - --fix Fixes tests. - - --bench Benchmarks the test(s). - - --time Times The test(s). - - --minified Runs test file(s) as minified. - - --stop Stop process if a test fails. - - -t, --tokens - Output a token stream instead of html. - - --pedantic - Conform to obscure parts of markdown.pl as much as possible. - Don't fix original markdown bugs. - - --gfm Enable github flavored markdown. - - --breaks - Enable GFM line breaks. Only works with the gfm option. - - --sanitize - Sanitize output. Ignore any HTML input. - - --smart-lists - Use smarter list behavior than the original markdown. - - --lang-prefix [prefix] - Set the prefix for code block classes. - - --mangle - Mangle email addresses. - - --no-sanitize, -no-etc... - The inverse of any of the marked options above. - - --silent - Silence error output. - - -h, --help - Display help information. - -CONFIGURATION - For configuring and running programmatically. - - Example - - require('marked')('*foo*', { gfm: true }); - -BUGS - Please report any bugs to https://github.com/markedjs/marked. - -LICENSE - Copyright (c) 2011-2014, Christopher Jeffrey (MIT License). - -SEE ALSO - markdown(1), node.js(1) - - marked(1) diff --git a/packages/markdown/marked/package-lock.json b/packages/markdown/marked/package-lock.json deleted file mode 100644 index 8218e8129..000000000 --- a/packages/markdown/marked/package-lock.json +++ /dev/null @@ -1,3626 +0,0 @@ -{ - "name": "marked", - "version": "0.8.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/compat-data": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.6.tgz", - "integrity": "sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==", - "dev": true, - "requires": { - "browserslist": "^4.8.5", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.7.tgz", - "integrity": "sha512-rBlqF3Yko9cynC5CCFy6+K/w2N+Sq/ff2BPy+Krp7rHlABIr5epbA7OxVeKoMHB39LZOp1UY5SuLjy6uWi35yA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.7", - "@babel/helpers": "^7.8.4", - "@babel/parser": "^7.8.7", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", - "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.7", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-call-delegate": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz", - "integrity": "sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.7" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz", - "integrity": "sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.8.6", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "dev": true, - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helpers": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", - "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", - "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz", - "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz", - "integrity": "sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz", - "integrity": "sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz", - "integrity": "sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz", - "integrity": "sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz", - "integrity": "sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz", - "integrity": "sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.8.tgz", - "integrity": "sha512-hC4Ld/Ulpf1psQciWWwdnUspQoQco2bMzSrwU6TmzRlvoYQe4rQFy9vnCZDTlVeCQj0JPfL+1RX0V8hCJvkgBA==", - "dev": true, - "requires": { - "@babel/helper-call-delegate": "^7.8.7", - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/preset-env": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.7.tgz", - "integrity": "sha512-BYftCVOdAYJk5ASsznKAUl53EMhfBbr8CJ1X+AJLfGPscQkwJFiaV/Wn9DPH/7fzm2v6iRYJKYHSqyynTGw0nw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.8.6", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.8.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.8.3", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.8.6", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.6", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.8.3", - "@babel/plugin-transform-modules-systemjs": "^7.8.3", - "@babel/plugin-transform-modules-umd": "^7.8.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/types": "^7.8.7", - "browserslist": "^4.8.5", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/runtime": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", - "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", - "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.6", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", - "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@markedjs/html-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@markedjs/html-differ/-/html-differ-3.0.0.tgz", - "integrity": "sha512-ubvgDumynqq6PnWyEPeBqLmEcrXR5w48AlQK8uj2M9IY04GNZhQGBL7sX2UI2IW8EKX5nRimFvv2iEKyPTSc4g==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "coa": "^2.0.2", - "diff": "^4.0.1", - "parse5-sax-parser": "^5.1.0" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "10.17.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", - "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==", - "dev": true - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - }, - "@types/qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - } - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browserslist": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.1.tgz", - "integrity": "sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001030", - "electron-to-chromium": "^1.3.363", - "node-releases": "^1.1.50" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001035", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", - "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "commenting": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/commenting/-/commenting-1.1.0.tgz", - "integrity": "sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==", - "dev": true - }, - "commonmark": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.1.tgz", - "integrity": "sha512-DafPdNYFXoEhsSiR4O+dJ45UJBfDL4cBTks4B+agKiaWt7qjG0bIhg5xuCE0RqU71ikJcBIf4/sRHh9vYQVF8Q==", - "dev": true, - "requires": { - "entities": "~1.1.1", - "mdurl": "~1.0.1", - "minimist": "~1.2.0", - "string.prototype.repeat": "^0.2.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "electron-to-chromium": { - "version": "1.3.376", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.376.tgz", - "integrity": "sha512-cv/PYVz5szeMz192ngilmezyPNFkUjuynuL2vNdiqIrio440nfTDdc0JJU0TS2KHLSVCs9gBbt4CFqM+HcBnjw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", - "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz", - "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", - "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-es": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", - "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", - "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-plugin-node": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz", - "integrity": "sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==", - "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", - "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", - "dev": true - }, - "eslint-plugin-standard": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", - "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", - "dev": true - }, - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", - "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "front-matter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-3.1.0.tgz", - "integrity": "sha512-RFEK8N6waWTdwBZOPNEtvwMjZ/hUfpwXkYUYkmmOhQGdhSulXhWrFwiUhdhkduLDiIwbROl/faF1X/PC/GGRMw==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true - }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "requires": { - "@types/node": "^10.0.3" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-reference": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", - "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", - "dev": true, - "requires": { - "@types/estree": "0.0.39" - } - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "jasmine": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.5.0.tgz", - "integrity": "sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "jasmine-core": "~3.5.0" - } - }, - "jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "dev": true, - "requires": { - "leven": "^3.1.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", - "dev": true - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", - "dev": true - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", - "dev": true - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", - "dev": true - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "markdown": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz", - "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=", - "dev": true, - "requires": { - "nopt": "~2.1.1" - } - }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "dependencies": { - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", - "dev": true - } - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "dev": true - }, - "node-releases": { - "version": "1.1.52", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.52.tgz", - "integrity": "sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "nopt": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", - "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "parse5-sax-parser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-5.1.1.tgz", - "integrity": "sha512-9HIh6zd7bF1NJe95LPCUC311CekdOi55R+HWXNCsGY6053DWaMijVKOv1oPvdvPTvFicifZyimBVJ6/qvG039Q==", - "dev": true, - "requires": { - "parse5": "^5.1.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "dev": true, - "requires": { - "asap": "~2.0.6" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, - "regenerator-transform": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.3.tgz", - "integrity": "sha512-zXHNKJspmONxBViAb3ZUmFoFPnTBs3zFhCEZJiwp/gkNzxVbTqNJVjYKx6Qk1tQ1P4XLf4TbH9+KBB7wGoAaUw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" - } - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.0.6.tgz", - "integrity": "sha512-P42IlI6a/bxh52ed8hEXXe44LcHfep2f26OZybMJPN1TTQftibvQEl3CWeOmJrzqGbFxOA000QXDWO9WJaOQpA==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - } - }, - "rollup-plugin-babel": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", - "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-license": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-0.13.0.tgz", - "integrity": "sha512-K1At1InQufYagn1zNTikWG6NorVjdBBoKtJdHqbyV/Z1ksM3wHtWlR/4rqdKxyZjTXNTDzM7mxn7j/HERexLFw==", - "dev": true, - "requires": { - "commenting": "1.1.0", - "glob": "7.1.6", - "lodash": "4.17.15", - "magic-string": "0.25.4", - "mkdirp": "0.5.1", - "moment": "2.24.0", - "spdx-expression-validate": "2.0.0", - "spdx-satisfies": "5.0.0" - }, - "dependencies": { - "magic-string": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", - "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - } - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "requires": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-expression-validate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-validate/-/spdx-expression-validate-2.0.0.tgz", - "integrity": "sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true - }, - "spdx-satisfies": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.0.tgz", - "integrity": "sha512-/hGhwh20BeGmkA+P/lm06RvXD94JduwNxtx/oX3B5ClPt1/u/m5MCaDNo1tV3Y9laLkQr/NRde63b9lLMhlNfw==", - "dev": true, - "requires": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "string.prototype.repeat": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=", - "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "requires": { - "get-port": "^3.1.0" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", - "dev": true - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "uglify-js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", - "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", - "dev": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vuln-regex-detector": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/vuln-regex-detector/-/vuln-regex-detector-1.3.0.tgz", - "integrity": "sha512-QWm8buVznZjdcfMuFHYsiNfHd0YQ7dO41G0iEGVPlUng5eZUo8uy+QsVCmbgVZ2b96xprY1Tz9dQD7QtvbFHXw==", - "dev": true, - "requires": { - "sync-request": "^6.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } - } -} diff --git a/packages/markdown/marked/package.json b/packages/markdown/marked/package.json deleted file mode 100644 index 6534e625f..000000000 --- a/packages/markdown/marked/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "marked", - "description": "A markdown parser built for speed", - "author": "Christopher Jeffrey", - "version": "0.8.0", - "main": "./src/marked.js", - "bin": "./bin/marked", - "man": "./man/marked.1", - "files": [ - "bin/", - "lib/", - "src/", - "man/", - "marked.min.js" - ], - "repository": "git://github.com/markedjs/marked.git", - "homepage": "https://marked.js.org", - "bugs": { - "url": "http://github.com/markedjs/marked/issues" - }, - "license": "MIT", - "keywords": [ - "markdown", - "markup", - "html" - ], - "tags": [ - "markdown", - "markup", - "html" - ], - "devDependencies": { - "@babel/core": "^7.8.7", - "@babel/preset-env": "^7.8.7", - "@markedjs/html-differ": "^3.0.0", - "cheerio": "^0.22.0", - "commonmark": "^0.29.1", - "eslint": "^6.8.0", - "eslint-config-standard": "^14.1.0", - "eslint-plugin-import": "^2.20.1", - "eslint-plugin-node": "^11.0.0", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-standard": "^4.0.1", - "front-matter": "^3.1.0", - "jasmine": "^3.5.0", - "markdown": "^0.5.0", - "markdown-it": "^10.0.0", - "node-fetch": "^2.6.0", - "rollup": "^2.0.6", - "rollup-plugin-babel": "^4.4.0", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-license": "^0.13.0", - "uglify-js": "^3.8.0", - "vuln-regex-detector": "^1.3.0" - }, - "scripts": { - "test": "jasmine --config=jasmine.json", - "test:all": "npm test && npm run test:lint", - "test:unit": "npm test -- test/unit/**/*-spec.js", - "test:specs": "npm test -- test/specs/**/*-spec.js", - "test:lint": "eslint bin/marked .", - "test:redos": "node test/vuln-regex.js", - "test:update": "node test/update-specs.js", - "bench": "npm run rollup && node test/bench.js", - "lint": "eslint --fix bin/marked .", - "build:reset": "git checkout upstream/master lib/marked.js lib/marked.esm.js marked.min.js", - "build": "npm run rollup && npm run minify", - "rollup": "npm run rollup:umd && npm run rollup:esm", - "rollup:umd": "rollup -c rollup.config.js", - "rollup:esm": "rollup -c rollup.config.esm.js", - "minify": "uglifyjs lib/marked.js -cm --comments /Copyright/ -o marked.min.js", - "preversion": "npm run build && (git diff --quiet || git commit -am 'build')" - }, - "engines": { - "node": ">= 8.16.2" - } -} diff --git a/packages/markdown/package.js b/packages/markdown/package.js old mode 100755 new mode 100644 index ac725b8f7..8116e2bc3 --- a/packages/markdown/package.js +++ b/packages/markdown/package.js @@ -1,24 +1,22 @@ -// Source: https://github.com/chjj/marked - Package.describe({ name: 'wekan-markdown', - summary: "GitHub flavored markdown parser for Meteor based on marked.js", - version: "1.0.7", - git: "https://github.com/wekan/markdown.git" + summary: 'GitHub flavored markdown parser for Meteor based on markdown-it', + version: '1.0.9', + git: 'https://github.com/wekan/markdown.git', }); // Before Meteor 0.9? if(!Package.onUse) Package.onUse = Package.on_use; Package.onUse(function (api) { - if(api.versionsFrom) api.versionsFrom('METEOR@0.9.0'); + if(api.versionsFrom) api.versionsFrom('1.8.2'); api.use('templating'); + api.use("ecmascript", ['server', 'client']); - api.add_files('marked/lib/marked.js', ['server', 'client']); - api.add_files('markdown.js', ['server', 'client']); api.export('Markdown', ['server', 'client']); - api.use("ui", "client", {weak: true}); - api.add_files("template-integration.js", "client"); + api.use('ui', 'client', {weak: true}); + + api.add_files('src/template-integration.js', 'client'); }); diff --git a/packages/markdown/smart.json b/packages/markdown/smart.json old mode 100755 new mode 100644 diff --git a/packages/markdown/src/template-integration.js b/packages/markdown/src/template-integration.js new file mode 100755 index 000000000..1e12ae794 --- /dev/null +++ b/packages/markdown/src/template-integration.js @@ -0,0 +1,78 @@ +import DOMPurify from 'dompurify'; + +var Markdown = require('markdown-it')({ + html: true, + linkify: true, + typographer: true, + breaks: true, +}); + +import markdownItMermaid from "@liradb2000/markdown-it-mermaid"; + +/* +// Static URL Scheme Listing +var urlschemes = [ + "aodroplink", + "thunderlink", + "cbthunderlink", + "onenote", + "file", + "abasurl", + "conisio", + "mailspring" +]; + +// Better would be a field in the admin backend to set this dynamically +// instead of putting all known or wanted url schemes here hard into code +// but i was not able to access those settings +// var urlschemes = currentSetting.automaticLinkedUrlSchemes.split('\n'); + +// put all url schemes into the linkify configuration to automatically make it clickable +for(var i=0; i`; + } else { + // use the default safeAttrValue function to process it + return sanitizeXss.safeAttrValue(tag, name, value, cssFilter); + } +}; +*/ + +var emoji = require('markdown-it-emoji'); +Markdown.use(emoji); +Markdown.use(markdownItMermaid); + +if (Package.ui) { + const Template = Package.templating.Template; + const UI = Package.ui.UI; + const HTML = Package.htmljs.HTML; + const Blaze = Package.blaze.Blaze; // implied by `ui` + + UI.registerHelper('markdown', new Template('markdown', function () { + const self = this; + let text = ''; + if (self.templateContentBlock) { + text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING); + } + + return HTML.Raw(DOMPurify.sanitize(Markdown.render(text), {ALLOW_UNKNOWN_PROTOCOLS: true})); + })); +} diff --git a/packages/markdown/template-integration.js b/packages/markdown/template-integration.js deleted file mode 100755 index 301bde31d..000000000 --- a/packages/markdown/template-integration.js +++ /dev/null @@ -1,16 +0,0 @@ -if (Package.ui) { - var Template = Package.templating.Template; - var UI = Package.ui.UI; - var HTML = Package.htmljs.HTML; - var Blaze = Package.blaze.Blaze; // implied by `ui` - - UI.registerHelper('markdown', new Template('markdown', function () { - var self = this; - var text = ""; - if(self.templateContentBlock) { - text = Blaze._toText(self.templateContentBlock, HTML.TEXTMODE.STRING); - } - - return HTML.Raw(Markdown(text)); - })); -} diff --git a/packages/wekan-accounts-cas/cas_server.js b/packages/wekan-accounts-cas/cas_server.js index 15c1b174e..c13a6df19 100644 --- a/packages/wekan-accounts-cas/cas_server.js +++ b/packages/wekan-accounts-cas/cas_server.js @@ -229,13 +229,13 @@ const casValidate = (req, ticket, token, service, callback) => { if (attrs.debug) { console.log(`CAS response : ${JSON.stringify(result)}`); } - let user = Meteor.users.findOne({ 'username': options.username }); + let user = Users.findOne({ 'username': options.username }); if (! user) { if (attrs.debug) { console.log(`Creating user account ${JSON.stringify(options)}`); } const userId = Accounts.insertUserDoc({}, options); - user = Meteor.users.findOne(userId); + user = Users.findOne(userId); } if (attrs.debug) { console.log(`Using user account ${JSON.stringify(user)}`); diff --git a/packages/wekan-accounts-lockout/CONTRIBUTING.md b/packages/wekan-accounts-lockout/CONTRIBUTING.md new file mode 100644 index 000000000..c72e88f69 --- /dev/null +++ b/packages/wekan-accounts-lockout/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing guide + +Want to contribute to Accounts-Lockout? Awesome! +There are many ways you can contribute, see below. + +## Opening issues + +Open an issue to report bugs or to propose new features. + +- Reporting bugs: describe the bug as clearly as you can, including steps to reproduce, what happened and what you were expecting to happen. Also include browser version, OS and other related software's (npm, Node.js, etc) versions when applicable. + +- Proposing features: explain the proposed feature, what it should do, why it is useful, how users should use it. Give us as much info as possible so it will be easier to discuss, access and implement the proposed feature. When you're unsure about a certain aspect of the feature, feel free to leave it open for others to discuss and find an appropriate solution. + +## Proposing pull requests + +Pull requests are very welcome. Note that if you are going to propose drastic changes, be sure to open an issue for discussion first, to make sure that your PR will be accepted before you spend effort coding it. + +Fork the Accounts-Lockout repository, clone it locally and create a branch for your proposed bug fix or new feature. Avoid working directly on the master branch. + +Implement your bug fix or feature, write tests to cover it and make sure all tests are passing (run a final `npm test` to make sure everything is correct). Then commit your changes, push your bug fix/feature branch to the origin (your forked repo) and open a pull request to the upstream (the repository you originally forked)'s master branch. + +## Documentation + +Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. +Please submit any and all improvements you can make to the repository's docs. diff --git a/packages/wekan-scrollbar/LICENSE b/packages/wekan-accounts-lockout/LICENSE similarity index 95% rename from packages/wekan-scrollbar/LICENSE rename to packages/wekan-accounts-lockout/LICENSE index c2d691582..a5298594c 100644 --- a/packages/wekan-scrollbar/LICENSE +++ b/packages/wekan-accounts-lockout/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2019 The Wekan Team +Copyright (c) 2017 Lucas Antoniassi de Paiva Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/wekan-accounts-lockout/README.md b/packages/wekan-accounts-lockout/README.md new file mode 100644 index 000000000..8d9c6897c --- /dev/null +++ b/packages/wekan-accounts-lockout/README.md @@ -0,0 +1,126 @@ +# Meteor - Accounts - Lockout + +[![Build Status](https://travis-ci.org/LucasAntoniassi/meteor-accounts-lockout.svg?branch=master)](https://travis-ci.org/LucasAntoniassi/meteor-accounts-lockout) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8ce60fa7e2c24891b9bdfc3b65433d23)](https://www.codacy.com/app/lucasantoniassi/meteor-accounts-lockout?utm_source=github.com&utm_medium=referral&utm_content=LucasAntoniassi/meteor-accounts-lockout&utm_campaign=Badge_Grade) +[![Code Climate](https://codeclimate.com/github/LucasAntoniassi/meteor-accounts-lockout/badges/gpa.svg)](https://codeclimate.com/github/LucasAntoniassi/meteor-accounts-lockout) + +## What it is + +Seamless Meteor apps accounts protection from password brute-force attacks. +Users won't notice it. Hackers shall not pass. + +![you-shall-not-pass](https://cloud.githubusercontent.com/assets/3399956/9023729/007dd2a2-38b1-11e5-807a-b81c6ce00c80.jpg) + +## Installation + +``` +meteor add lucasantoniassi:accounts-lockout +``` + +## Usage via ES6 import + +```javascript +// server +import { AccountsLockout } from 'meteor/lucasantoniassi:accounts-lockout'; +``` + +## How to use + +Default settings: + +```javascript + "knownUsers": { + "failuresBeforeLockout": 3, // positive integer greater than 0 + "lockoutPeriod": 60, // in seconds + "failureWindow": 10 // in seconds + }, + "unknownUsers": { + "failuresBeforeLockout": 3, // positive integer greater than 0 + "lockoutPeriod": 60, // in seconds + "failureWindow": 10 // in seconds + } +``` + +`knownUsers` are users where already belongs to your `Meteor.users` collections, +these rules are applied if they attempt to login with an incorrect password but a know email. + +`unknownUsers` are users where **not** belongs to your `Meteor.users` collections, +these rules are applied if they attempt to login with a unknown email. + +`failuresBeforeLockout` should be a positive integer greater than 0. + +`lockoutPeriod` should be in seconds. + +`failureWindow` should be in seconds. + +If the `default` is nice to you, you can do that. + +```javascript +(new AccountsLockout()).startup(); +``` + +You can overwrite passing an `object` as argument. + +```javascript +(new AccountsLockout({ + knownUsers: { + failuresBeforeLockout: 3, + lockoutPeriod: 60, + failureWindow: 15, + }, + unknownUsers: { + failuresBeforeLockout: 3, + lockoutPeriod: 60, + failureWindow: 15, + }, +})).startup(); +``` + +If you prefer, you can pass a `function` as argument. + +```javascript +const knownUsersRules = (user) => { + // apply some logic with this user + return { + failuresBeforeLockout, + lockoutPeriod, + failureWindow, + }; +}; + +const unknownUsersRules = (connection) => { + // apply some logic with this connection + return { + failuresBeforeLockout, + lockoutPeriod, + failureWindow, + }; +}; + +(new AccountsLockout({ + knownUsers: knownUsersRules, + unknownUsers: unknownUsersRules, +})).startup(); +``` + +If you prefer, you can use `Meteor.settings`. It will overwrite any previous case. + +```javascript +"accounts-lockout": { + "knownUsers": { + "failuresBeforeLockout": 3, + "lockoutPeriod": 60, + "failureWindow": 10 + }, + "unknownUsers": { + "failuresBeforeLockout": 3, + "lockoutPeriod": 60, + "failureWindow": 10 + } +} +``` + +## License + +This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). + diff --git a/packages/wekan-accounts-lockout/accounts-lockout.js b/packages/wekan-accounts-lockout/accounts-lockout.js new file mode 100644 index 000000000..0c781c802 --- /dev/null +++ b/packages/wekan-accounts-lockout/accounts-lockout.js @@ -0,0 +1,5 @@ +import AccountsLockout from './src/accountsLockout'; + +const Name = 'wekan-accounts-lockout'; + +export { Name, AccountsLockout }; diff --git a/packages/wekan-accounts-lockout/package.js b/packages/wekan-accounts-lockout/package.js new file mode 100644 index 000000000..e2bda76a6 --- /dev/null +++ b/packages/wekan-accounts-lockout/package.js @@ -0,0 +1,18 @@ +/* global Package */ + +Package.describe({ + name: 'wekan-accounts-lockout', + version: '1.0.0', + summary: 'Meteor package for locking user accounts and stopping brute force attacks', + git: 'https://github.com/lucasantoniassi/meteor-accounts-lockout.git', + documentation: 'README.md', +}); + +Package.onUse((api) => { + api.versionsFrom('1.4.2.3'); + api.use([ + 'ecmascript', + 'accounts-password', + ]); + api.mainModule('accounts-lockout.js'); +}); diff --git a/packages/wekan-accounts-lockout/package.json b/packages/wekan-accounts-lockout/package.json new file mode 100644 index 000000000..bbff88655 --- /dev/null +++ b/packages/wekan-accounts-lockout/package.json @@ -0,0 +1,4 @@ +{ + "name": "wekan-accounts-lockout", + "private": true +} diff --git a/packages/wekan-accounts-lockout/src/accountsLockout.js b/packages/wekan-accounts-lockout/src/accountsLockout.js new file mode 100644 index 000000000..8a1f05ea1 --- /dev/null +++ b/packages/wekan-accounts-lockout/src/accountsLockout.js @@ -0,0 +1,29 @@ +import KnownUser from './knownUser'; +import UnknownUser from './unknownUser'; + +class AccountsLockout { + constructor({ + knownUsers = { + failuresBeforeLockout: 3, + lockoutPeriod: 60, + failureWindow: 15, + }, + unknownUsers = { + failuresBeforeLockout: 3, + lockoutPeriod: 60, + failureWindow: 15, + }, + }) { + this.settings = { + knownUsers, + unknownUsers, + }; + } + + startup() { + (new KnownUser(this.settings.knownUsers)).startup(); + (new UnknownUser(this.settings.unknownUsers)).startup(); + } +} + +export default AccountsLockout; diff --git a/packages/wekan-accounts-lockout/src/accountsLockoutCollection.js b/packages/wekan-accounts-lockout/src/accountsLockoutCollection.js new file mode 100644 index 000000000..9a885a0f7 --- /dev/null +++ b/packages/wekan-accounts-lockout/src/accountsLockoutCollection.js @@ -0,0 +1,3 @@ +import { Meteor } from 'meteor/meteor'; + +export default new Meteor.Collection('AccountsLockout.Connections'); diff --git a/packages/wekan-accounts-lockout/src/knownUser.js b/packages/wekan-accounts-lockout/src/knownUser.js new file mode 100644 index 000000000..81558e1b8 --- /dev/null +++ b/packages/wekan-accounts-lockout/src/knownUser.js @@ -0,0 +1,321 @@ +/* eslint-disable no-underscore-dangle */ + +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +class KnownUser { + constructor(settings) { + this.unchangedSettings = settings; + this.settings = settings; + } + + startup() { + if (!(this.unchangedSettings instanceof Function)) { + this.updateSettings(); + } + this.scheduleUnlocksForLockedAccounts(); + KnownUser.unlockAccountsIfLockoutAlreadyExpired(); + this.hookIntoAccounts(); + } + + updateSettings() { + const settings = KnownUser.knownUsers(); + if (settings) { + settings.forEach(function updateSetting({ key, value }) { + this.settings[key] = value; + }); + } + this.validateSettings(); + } + + validateSettings() { + if ( + !this.settings.failuresBeforeLockout || + this.settings.failuresBeforeLockout < 0 + ) { + throw new Error('"failuresBeforeLockout" is not positive integer'); + } + if ( + !this.settings.lockoutPeriod || + this.settings.lockoutPeriod < 0 + ) { + throw new Error('"lockoutPeriod" is not positive integer'); + } + if ( + !this.settings.failureWindow || + this.settings.failureWindow < 0 + ) { + throw new Error('"failureWindow" is not positive integer'); + } + } + + scheduleUnlocksForLockedAccounts() { + const lockedAccountsCursor = Meteor.users.find( + { + 'services.accounts-lockout.unlockTime': { + $gt: Number(new Date()), + }, + }, + { + fields: { + 'services.accounts-lockout.unlockTime': 1, + }, + }, + ); + const currentTime = Number(new Date()); + lockedAccountsCursor.forEach((user) => { + let lockDuration = KnownUser.unlockTime(user) - currentTime; + if (lockDuration >= this.settings.lockoutPeriod) { + lockDuration = this.settings.lockoutPeriod * 1000; + } + if (lockDuration <= 1) { + lockDuration = 1; + } + Meteor.setTimeout( + KnownUser.unlockAccount.bind(null, user._id), + lockDuration, + ); + }); + } + + static unlockAccountsIfLockoutAlreadyExpired() { + const currentTime = Number(new Date()); + const query = { + 'services.accounts-lockout.unlockTime': { + $lt: currentTime, + }, + }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + Meteor.users.update(query, data); + } + + hookIntoAccounts() { + Accounts.validateLoginAttempt(this.validateLoginAttempt.bind(this)); + Accounts.onLogin(KnownUser.onLogin); + } + + + validateLoginAttempt(loginInfo) { + if ( + // don't interrupt non-password logins + loginInfo.type !== 'password' || + loginInfo.user === undefined || + // Don't handle errors unless they are due to incorrect password + (loginInfo.error !== undefined && loginInfo.error.reason !== 'Incorrect password') + ) { + return loginInfo.allowed; + } + + // If there was no login error and the account is NOT locked, don't interrupt + const unlockTime = KnownUser.unlockTime(loginInfo.user); + if (loginInfo.error === undefined && unlockTime === 0) { + return loginInfo.allowed; + } + + if (this.unchangedSettings instanceof Function) { + this.settings = this.unchangedSettings(loginInfo.user); + this.validateSettings(); + } + + const userId = loginInfo.user._id; + let failedAttempts = 1 + KnownUser.failedAttempts(loginInfo.user); + const firstFailedAttempt = KnownUser.firstFailedAttempt(loginInfo.user); + const currentTime = Number(new Date()); + + const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow); + if (canReset) { + failedAttempts = 1; + KnownUser.resetAttempts(failedAttempts, userId); + } + + const canIncrement = failedAttempts < this.settings.failuresBeforeLockout; + if (canIncrement) { + KnownUser.incrementAttempts(failedAttempts, userId); + } + + const maxAttemptsAllowed = this.settings.failuresBeforeLockout; + const attemptsRemaining = maxAttemptsAllowed - failedAttempts; + if (unlockTime > currentTime) { + let duration = unlockTime - currentTime; + duration = Math.ceil(duration / 1000); + duration = duration > 1 ? duration : 1; + KnownUser.tooManyAttempts(duration); + } + if (failedAttempts === maxAttemptsAllowed) { + this.setNewUnlockTime(failedAttempts, userId); + + let duration = this.settings.lockoutPeriod; + duration = Math.ceil(duration); + duration = duration > 1 ? duration : 1; + return KnownUser.tooManyAttempts(duration); + } + return KnownUser.incorrectPassword( + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + ); + } + + static resetAttempts( + failedAttempts, + userId, + ) { + const currentTime = Number(new Date()); + const query = { _id: userId }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + 'services.accounts-lockout.firstFailedAttempt': currentTime, + }, + }; + Meteor.users.update(query, data); + } + + static incrementAttempts( + failedAttempts, + userId, + ) { + const currentTime = Number(new Date()); + const query = { _id: userId }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + }, + }; + Meteor.users.update(query, data); + } + + setNewUnlockTime( + failedAttempts, + userId, + ) { + const currentTime = Number(new Date()); + const newUnlockTime = (1000 * this.settings.lockoutPeriod) + currentTime; + const query = { _id: userId }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + 'services.accounts-lockout.unlockTime': newUnlockTime, + }, + }; + Meteor.users.update(query, data); + Meteor.setTimeout( + KnownUser.unlockAccount.bind(null, userId), + this.settings.lockoutPeriod * 1000, + ); + } + + static onLogin(loginInfo) { + if (loginInfo.type !== 'password') { + return; + } + const userId = loginInfo.user._id; + const query = { _id: userId }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + Meteor.users.update(query, data); + } + + static incorrectPassword( + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + ) { + throw new Meteor.Error( + 403, + 'Incorrect password', + JSON.stringify({ + message: 'Incorrect password', + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + }), + ); + } + + static tooManyAttempts(duration) { + throw new Meteor.Error( + 403, + 'Too many attempts', + JSON.stringify({ + message: 'Wrong passwords were submitted too many times. Account is locked for a while.', + duration, + }), + ); + } + + static knownUsers() { + let knownUsers; + try { + knownUsers = Meteor.settings['accounts-lockout'].knownUsers; + } catch (e) { + knownUsers = false; + } + return knownUsers || false; + } + + static unlockTime(user) { + let unlockTime; + try { + unlockTime = user.services['accounts-lockout'].unlockTime; + } catch (e) { + unlockTime = 0; + } + return unlockTime || 0; + } + + static failedAttempts(user) { + let failedAttempts; + try { + failedAttempts = user.services['accounts-lockout'].failedAttempts; + } catch (e) { + failedAttempts = 0; + } + return failedAttempts || 0; + } + + static lastFailedAttempt(user) { + let lastFailedAttempt; + try { + lastFailedAttempt = user.services['accounts-lockout'].lastFailedAttempt; + } catch (e) { + lastFailedAttempt = 0; + } + return lastFailedAttempt || 0; + } + + static firstFailedAttempt(user) { + let firstFailedAttempt; + try { + firstFailedAttempt = user.services['accounts-lockout'].firstFailedAttempt; + } catch (e) { + firstFailedAttempt = 0; + } + return firstFailedAttempt || 0; + } + + static unlockAccount(userId) { + const query = { _id: userId }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + Meteor.users.update(query, data); + } +} + +export default KnownUser; diff --git a/packages/wekan-accounts-lockout/src/unknownUser.js b/packages/wekan-accounts-lockout/src/unknownUser.js new file mode 100644 index 000000000..443507c82 --- /dev/null +++ b/packages/wekan-accounts-lockout/src/unknownUser.js @@ -0,0 +1,329 @@ +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; +import _AccountsLockoutCollection from './accountsLockoutCollection'; + +class UnknownUser { + constructor( + settings, + { + AccountsLockoutCollection = _AccountsLockoutCollection, + } = {}, + ) { + this.AccountsLockoutCollection = AccountsLockoutCollection; + this.settings = settings; + } + + startup() { + if (!(this.settings instanceof Function)) { + this.updateSettings(); + } + this.scheduleUnlocksForLockedAccounts(); + this.unlockAccountsIfLockoutAlreadyExpired(); + this.hookIntoAccounts(); + } + + updateSettings() { + const settings = UnknownUser.unknownUsers(); + if (settings) { + settings.forEach(function updateSetting({ key, value }) { + this.settings[key] = value; + }); + } + this.validateSettings(); + } + + validateSettings() { + if ( + !this.settings.failuresBeforeLockout || + this.settings.failuresBeforeLockout < 0 + ) { + throw new Error('"failuresBeforeLockout" is not positive integer'); + } + if ( + !this.settings.lockoutPeriod || + this.settings.lockoutPeriod < 0 + ) { + throw new Error('"lockoutPeriod" is not positive integer'); + } + if ( + !this.settings.failureWindow || + this.settings.failureWindow < 0 + ) { + throw new Error('"failureWindow" is not positive integer'); + } + } + + scheduleUnlocksForLockedAccounts() { + const lockedAccountsCursor = this.AccountsLockoutCollection.find( + { + 'services.accounts-lockout.unlockTime': { + $gt: Number(new Date()), + }, + }, + { + fields: { + 'services.accounts-lockout.unlockTime': 1, + }, + }, + ); + const currentTime = Number(new Date()); + lockedAccountsCursor.forEach((connection) => { + let lockDuration = this.unlockTime(connection) - currentTime; + if (lockDuration >= this.settings.lockoutPeriod) { + lockDuration = this.settings.lockoutPeriod * 1000; + } + if (lockDuration <= 1) { + lockDuration = 1; + } + Meteor.setTimeout( + this.unlockAccount.bind(this, connection.clientAddress), + lockDuration, + ); + }); + } + + unlockAccountsIfLockoutAlreadyExpired() { + const currentTime = Number(new Date()); + const query = { + 'services.accounts-lockout.unlockTime': { + $lt: currentTime, + }, + }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + this.AccountsLockoutCollection.update(query, data); + } + + hookIntoAccounts() { + Accounts.validateLoginAttempt(this.validateLoginAttempt.bind(this)); + Accounts.onLogin(this.onLogin.bind(this)); + } + + validateLoginAttempt(loginInfo) { + // don't interrupt non-password logins + if ( + loginInfo.type !== 'password' || + loginInfo.user !== undefined || + loginInfo.error === undefined || + loginInfo.error.reason !== 'User not found' + ) { + return loginInfo.allowed; + } + + if (this.settings instanceof Function) { + this.settings = this.settings(loginInfo.connection); + this.validateSettings(); + } + + const clientAddress = loginInfo.connection.clientAddress; + const unlockTime = this.unlockTime(loginInfo.connection); + let failedAttempts = 1 + this.failedAttempts(loginInfo.connection); + const firstFailedAttempt = this.firstFailedAttempt(loginInfo.connection); + const currentTime = Number(new Date()); + + const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow); + if (canReset) { + failedAttempts = 1; + this.resetAttempts(failedAttempts, clientAddress); + } + + const canIncrement = failedAttempts < this.settings.failuresBeforeLockout; + if (canIncrement) { + this.incrementAttempts(failedAttempts, clientAddress); + } + + const maxAttemptsAllowed = this.settings.failuresBeforeLockout; + const attemptsRemaining = maxAttemptsAllowed - failedAttempts; + if (unlockTime > currentTime) { + let duration = unlockTime - currentTime; + duration = Math.ceil(duration / 1000); + duration = duration > 1 ? duration : 1; + UnknownUser.tooManyAttempts(duration); + } + if (failedAttempts === maxAttemptsAllowed) { + this.setNewUnlockTime(failedAttempts, clientAddress); + + let duration = this.settings.lockoutPeriod; + duration = Math.ceil(duration); + duration = duration > 1 ? duration : 1; + return UnknownUser.tooManyAttempts(duration); + } + return UnknownUser.userNotFound( + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + ); + } + + resetAttempts( + failedAttempts, + clientAddress, + ) { + const currentTime = Number(new Date()); + const query = { clientAddress }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + 'services.accounts-lockout.firstFailedAttempt': currentTime, + }, + }; + this.AccountsLockoutCollection.upsert(query, data); + } + + incrementAttempts( + failedAttempts, + clientAddress, + ) { + const currentTime = Number(new Date()); + const query = { clientAddress }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + }, + }; + this.AccountsLockoutCollection.upsert(query, data); + } + + setNewUnlockTime( + failedAttempts, + clientAddress, + ) { + const currentTime = Number(new Date()); + const newUnlockTime = (1000 * this.settings.lockoutPeriod) + currentTime; + const query = { clientAddress }; + const data = { + $set: { + 'services.accounts-lockout.failedAttempts': failedAttempts, + 'services.accounts-lockout.lastFailedAttempt': currentTime, + 'services.accounts-lockout.unlockTime': newUnlockTime, + }, + }; + this.AccountsLockoutCollection.upsert(query, data); + Meteor.setTimeout( + this.unlockAccount.bind(this, clientAddress), + this.settings.lockoutPeriod * 1000, + ); + } + + onLogin(loginInfo) { + if (loginInfo.type !== 'password') { + return; + } + const clientAddress = loginInfo.connection.clientAddress; + const query = { clientAddress }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + this.AccountsLockoutCollection.update(query, data); + } + + static userNotFound( + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + ) { + throw new Meteor.Error( + 403, + 'User not found', + JSON.stringify({ + message: 'User not found', + failedAttempts, + maxAttemptsAllowed, + attemptsRemaining, + }), + ); + } + + static tooManyAttempts(duration) { + throw new Meteor.Error( + 403, + 'Too many attempts', + JSON.stringify({ + message: 'Wrong emails were submitted too many times. Account is locked for a while.', + duration, + }), + ); + } + + static unknownUsers() { + let unknownUsers; + try { + unknownUsers = Meteor.settings['accounts-lockout'].unknownUsers; + } catch (e) { + unknownUsers = false; + } + return unknownUsers || false; + } + + findOneByConnection(connection) { + return this.AccountsLockoutCollection.findOne({ + clientAddress: connection.clientAddress, + }); + } + + unlockTime(connection) { + connection = this.findOneByConnection(connection); + let unlockTime; + try { + unlockTime = connection.services['accounts-lockout'].unlockTime; + } catch (e) { + unlockTime = 0; + } + return unlockTime || 0; + } + + failedAttempts(connection) { + connection = this.findOneByConnection(connection); + let failedAttempts; + try { + failedAttempts = connection.services['accounts-lockout'].failedAttempts; + } catch (e) { + failedAttempts = 0; + } + return failedAttempts || 0; + } + + lastFailedAttempt(connection) { + connection = this.findOneByConnection(connection); + let lastFailedAttempt; + try { + lastFailedAttempt = connection.services['accounts-lockout'].lastFailedAttempt; + } catch (e) { + lastFailedAttempt = 0; + } + return lastFailedAttempt || 0; + } + + firstFailedAttempt(connection) { + connection = this.findOneByConnection(connection); + let firstFailedAttempt; + try { + firstFailedAttempt = connection.services['accounts-lockout'].firstFailedAttempt; + } catch (e) { + firstFailedAttempt = 0; + } + return firstFailedAttempt || 0; + } + + unlockAccount(clientAddress) { + const query = { clientAddress }; + const data = { + $unset: { + 'services.accounts-lockout.unlockTime': 0, + 'services.accounts-lockout.failedAttempts': 0, + }, + }; + this.AccountsLockoutCollection.update(query, data); + } +} + +export default UnknownUser; diff --git a/packages/wekan-cfs-access-point/.travis.yml b/packages/wekan-cfs-access-point/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-access-point/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-access-point/CHANGELOG.md b/packages/wekan-cfs-access-point/CHANGELOG.md new file mode 100644 index 000000000..759f22a03 --- /dev/null +++ b/packages/wekan-cfs-access-point/CHANGELOG.md @@ -0,0 +1,288 @@ +# Changelog + +## [v0.1.50] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46) +#### 21/1/19 by Harry Adel + +- Bump to version 0.1.50 + +- *Merged pull-request:* "filename conversion for FS.HTTP.Handlers.Get" [#9](https://github.com/zcfs/Meteor-CollectionFS/pull/994) ([yatusiter](https://github.com/yatusiter)) + + + +## [v0.1.46] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46) +#### 30/3/15 by Eric Dobbertin + +- Bump to version 0.1.46 + +- *Merged pull-request:* [#611](https://github.com/zcfs/Meteor-CollectionFS/issues/611) + +- Exposed request handlers on `FS.HTTP.Handlers` object so that app can override + +## [v0.1.43] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.43) +#### 20/12/14 by Morten Henriksen +- add changelog + +- Bump to version 0.1.43 + +- *Fixed bug:* "Doesn't work in IE 8" [#10](https://github.com/zcfs/Meteor-cfs-access-point/issues/10) + +- *Merged pull-request:* "rootUrlPathPrefix fix for cordova" [#9](https://github.com/zcfs/Meteor-cfs-access-point/issues/9) ([dmitriyles](https://github.com/dmitriyles)) + +- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000)) + +Patches by GitHub users [@dmitriyles](https://github.com/dmitriyles), [@tanis2000](https://github.com/tanis2000). + +## [v0.1.42] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.42) +#### 17/12/14 by Morten Henriksen +## [v0.1.41] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.41) +#### 17/12/14 by Morten Henriksen +- mbr update, remove versions.json + +- Cordova rootUrlPathPrefix fix + +- Bump to version 0.1.41 + +## [v0.1.40] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.40) +#### 17/12/14 by Morten Henriksen +- mbr fixed warnings + +- fixes to GET handler + +- add back tests + +- support apps in server subdirectories; closes #8 + +- 0.9.1 support + +## [v0.0.39] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.39) +#### 28/08/14 by Morten Henriksen +- Meteor Package System Update + +## [v0.0.38] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.38) +#### 27/08/14 by Eric Dobbertin +## [v0.0.37] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.37) +#### 26/08/14 by Eric Dobbertin +- change package name to lowercase + +## [v0.0.36] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.36) +#### 06/08/14 by Eric Dobbertin +- pass correct arg + +## [v0.0.35] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.35) +#### 06/08/14 by Eric Dobbertin +- move to correct place + +## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.34) +#### 05/08/14 by Eric Dobbertin +- *Merged pull-request:* "Added contentLength for ranges and inline content" [#5](https://github.com/zcfs/Meteor-cfs-access-point/issues/5) ([maomorales](https://github.com/maomorales)) + +- Content-Length and Last-Modified headers + +Patches by GitHub user [@maomorales](https://github.com/maomorales). + +## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.33) +#### 31/07/14 by Eric Dobbertin +- *Merged pull-request:* "Force browser to download with filename passed in url" [#3](https://github.com/zcfs/Meteor-cfs-access-point/issues/3) ([elbowz](https://github.com/elbowz)) + +- Force browser to download with filename passed in url + +Patches by GitHub user [@elbowz](https://github.com/elbowz). + +## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.32) +#### 28/07/14 by Eric Dobbertin +- support collection-specific GET headers + +- update API docs + +## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.31) +#### 06/07/14 by Eric Dobbertin +- allow override filename + +## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.30) +#### 30/04/14 by Eric Dobbertin +- ignore auth on server so that url method can be called on the server + +## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.29) +#### 30/04/14 by Eric Dobbertin +- rework the new authtoken stuff to make it easier to debug and cleaner + +## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.28) +#### 29/04/14 by Eric Dobbertin +- generate api docs + +- adjustments to use new FS.File API functions, plus have `url` function omit query string whenever possible + +- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000)) + +- Switched to HTTP.call() to get the server time + +- Better check for options.auth being a number. Check to see if we have Buffer() available on the server side. New check to make sure we have the token. Switched Metheor.method to HTTP.methods for the getServerTime() function. + +- Expiration is now optional. If auth is set to a number, that is the number of seconds the token is valid for. + +- Added time sync with the server for token generation. + +- Added code to pass a token with a set expiration date from the client. Added token check on the server side. + +Patches by GitHub user [@tanis2000](https://github.com/tanis2000). + +## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.27) +#### 08/04/14 by Eric Dobbertin +- clean up/fix whole-file upload handler + +## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.26) +#### 07/04/14 by Eric Dobbertin +- add URL options to get temporary images while uploading and storing + +## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.25) +#### 03/04/14 by Eric Dobbertin +- * allow `setBaseUrl` to be called either outside of Meteor.startup or inside * move encodeParams helper to FS.Utility + +## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.24) +#### 03/04/14 by Eric Dobbertin +- properly remount URLs + +- when uploading chunks, check the insert allow/deny since it's part of inserting + +## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.23) +#### 31/03/14 by Eric Dobbertin +- use latest releases + +## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.22) +#### 29/03/14 by Morten Henriksen +- remove underscore deps + +## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.21) +#### 25/03/14 by Morten Henriksen +- add comments about shareId + +## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.20) +#### 23/03/14 by Morten Henriksen +- Rollback to specific git dependency + +- Try modified test script + +- deps are already in collectionFS + +## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.19) +#### 22/03/14 by Morten Henriksen +- try to fix travis test by using general package references + +## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.18) +#### 22/03/14 by Morten Henriksen +- If the read stream fails we send an error to the client + +## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.17) +#### 21/03/14 by Morten Henriksen +- remove smart lock + +- commit smart.lock, trying to get tests to pass on travis + +- some minor pkg adjustments; trying to get tests to pass on travis + +## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.16) +#### 18/03/14 by Morten Henriksen +- Rollback to using the direct storage adapter - makes more sense when serving files + +- shift to new http.methods streaming api + +- move server side DDP access points to cfs-download-ddp pkg; update API docs + +- fix typo... + +- return something useful + +- convert to streaming + +- Add streaming WIP + +- fix/adjust some tests; minor improvements to some handlers + +- Add unmount and allow mount to use default selector function + +- Refactor access point - wip + +## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.15) +#### 05/03/14 by Morten Henriksen +- Refactor note, encode stuff should be prefixed into FS.Utility + +- FS.File.url add user deps when auth is used + +- fix url method + +- query string fix + +- move PUT access points for HTTP upload into this package; mount DELETE on /record/ as well as /files/; some fixes and improvements to handlers + +## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.14) +#### 03/03/14 by Eric Dobbertin +- better error; return Buffer instead of converting to Uint8Array + +## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.13) +#### 02/03/14 by Eric Dobbertin +- more tests, make everything work, add unpublish method + +- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point + +## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.12) +#### 01/03/14 by Eric Dobbertin +- add travis-ci image + +- rework URLs a bit, use http-publish package to publish FS.Collection listing, and add a test for this (!) + +- add http-publish dependency + +- del should be delete + +## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.11) +#### 28/02/14 by Eric Dobbertin +- move some code to other packages; redo the HTTP GET/DEL methods + +## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.10) +#### 28/02/14 by Eric Dobbertin +- move DDP upload methods to new cfs-upload-ddp package + +## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.9) +#### 21/02/14 by Eric Dobbertin +- new URL syntax; use the store's file key instead of ID; also fix allow/deny checks with insecure + +## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.8) +#### 20/02/14 by Eric Dobbertin +- support HTTP PUT of new file and fix PUT of existing file + +## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.7) +#### 17/02/14 by Morten Henriksen +- add http-methods dependency + +## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.6) +#### 16/02/14 by Morten Henriksen +## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.5) +#### 16/02/14 by Morten Henriksen +- a few fixes and improvements + +- need to actually mount it + +- attempt at switching to generic HTTP access point; also add support for chunked http downloads (range header) + +## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.4) +#### 15/02/14 by Morten Henriksen +- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point + +- corrected typo + +- added debugging + +- call HTTP.methods on server only + +- run client side, too, for side effects + +- rework for additional abstraction; also DDP methods don't need to be per-collection so they no longer are + +## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.3) +#### 13/02/14 by Morten Henriksen +## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.2) +#### 13/02/14 by Morten Henriksen +## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.1) +#### 13/02/14 by Morten Henriksen +- init commit + diff --git a/packages/wekan-cfs-access-point/LICENSE.md b/packages/wekan-cfs-access-point/LICENSE.md new file mode 100644 index 000000000..1a3820821 --- /dev/null +++ b/packages/wekan-cfs-access-point/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-access-point/README.md b/packages/wekan-cfs-access-point/README.md new file mode 100644 index 000000000..fe7583265 --- /dev/null +++ b/packages/wekan-cfs-access-point/README.md @@ -0,0 +1,32 @@ +wekan-cfs-access-point [![Build Status](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point) +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. You could potentially use your own access point +package instead. + +## Define a URL for Collection Listing + +To define a URL that accepts GET requests and returns a list of published +files in a FS.Collection: + +```js +Images = new FS.Collection("images", { + stores: [myStore] +}); + +FS.HTTP.publish(Images, function () { + // `this` provides a context similar to Meteor.publish + return Images.find(); +}); +``` + +The URL will be '/cfs/record/images', where the `cfs` piece is configurable +using the `FS.HTTP.setBaseUrl` method. + +## API Documentation + +[Here](api.md) diff --git a/packages/wekan-cfs-access-point/access-point-client.js b/packages/wekan-cfs-access-point/access-point-client.js new file mode 100644 index 000000000..bc25d0bca --- /dev/null +++ b/packages/wekan-cfs-access-point/access-point-client.js @@ -0,0 +1,58 @@ +FS.HTTP.setHeadersForGet = function setHeadersForGet() { + // Client Stub +}; + +FS.HTTP.now = function() { + return new Date(new Date() + FS.HTTP._serverTimeDiff); +}; + +// Returns the localstorage if its found and working +// TODO: check if this works in IE +// could use Meteor._localStorage - just needs a rewrite +FS.HTTP._storage = function() { + var storage, + fail, + uid; + try { + uid = "test"; + (storage = window.localStorage).setItem(uid, uid); + fail = (storage.getItem(uid) !== uid); + storage.removeItem(uid); + if (fail) { + storage = false; + } + } catch(e) { + console.log("Error initializing storage for FS.HTTP"); + console.log(e); + } + + return storage; +}; + +// get our storage if found +FS.HTTP.storage = FS.HTTP._storage(); + +FS.HTTP._prefix = 'fsHTTP.'; + +FS.HTTP._serverTimeDiff = 0; // Time difference in ms + +if (FS.HTTP.storage) { + // Initialize the FS.HTTP._serverTimeDiff + FS.HTTP._serverTimeDiff = (1*FS.HTTP.storage.getItem(FS.HTTP._prefix+'timeDiff')) || 0; + // At client startup we figure out the time difference between server and + // client time - this includes lag and timezone + Meteor.startup(function() { + // Call the server method an get server time + HTTP.get(rootUrlPathPrefix + '/cfs/servertime', function(error, result) { + if (!error) { + // Update our server time diff + var dateNew = new Date(+result.content); + FS.HTTP._serverTimeDiff = dateNew - new Date();// - lag or/and timezone + // Update the localstorage + FS.HTTP.storage.setItem(FS.HTTP._prefix + 'timeDiff', FS.HTTP._serverTimeDiff); + } else { + console.log(error.message); + } + }); // EO Server call + }); +} diff --git a/packages/wekan-cfs-access-point/access-point-common.js b/packages/wekan-cfs-access-point/access-point-common.js new file mode 100644 index 000000000..bccf7c14c --- /dev/null +++ b/packages/wekan-cfs-access-point/access-point-common.js @@ -0,0 +1,199 @@ +rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; +// Adjust the rootUrlPathPrefix if necessary +if (rootUrlPathPrefix.length > 0) { + if (rootUrlPathPrefix.slice(0, 1) !== '/') { + rootUrlPathPrefix = '/' + rootUrlPathPrefix; + } + if (rootUrlPathPrefix.slice(-1) === '/') { + rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1); + } +} + +// prepend ROOT_URL when isCordova +if (Meteor.isCordova) { + rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, ''); +} + +baseUrl = '/cfs'; +FS.HTTP = FS.HTTP || {}; + +// Note the upload URL so that client uploader packages know what it is +FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; + +/** + * @method FS.HTTP.setBaseUrl + * @public + * @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints. + * @returns {undefined} + */ +FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { + + // Adjust the baseUrl if necessary + if (newBaseUrl.slice(0, 1) !== '/') { + newBaseUrl = '/' + newBaseUrl; + } + if (newBaseUrl.slice(-1) === '/') { + newBaseUrl = newBaseUrl.slice(0, -1); + } + + // Update the base URL + baseUrl = newBaseUrl; + + // Change the upload URL so that client uploader packages know what it is + FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; + + // Remount URLs with the new baseUrl, unmounting the old, on the server only. + // If existingMountPoints is empty, then we haven't run the server startup + // code yet, so this new URL will be used at that point for the initial mount. + if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) { + mountUrls(); + } +}; + +/* + * FS.File extensions + */ + +/** + * @method FS.File.prototype.urlRelative Construct the file url + * @public + * @param {Object} [options] + * @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used. + * @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds. + * @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser. + * @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet. + * @param {Boolean} [options.returnWhenStored=false] Flag relevant only on server, Return the URL only when file has been saved to the requested store. + * @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself. + * @param {String} [options.uploading=null] A URL to return while the file is being uploaded. + * @param {String} [options.storing=null] A URL to return while the file is being stored. + * @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store. + * + * Returns the relative HTTP URL for getting the file or its metadata. + */ +FS.File.prototype.urlRelative = function(options) { + var self = this; + options = options || {}; + options = FS.Utility.extend({ + store: null, + auth: null, + download: false, + metadata: false, + brokenIsFine: false, + returnWhenStored: false, + uploading: null, // return this URL while uploading + storing: null, // return this URL while storing + filename: null // override the filename that is shown to the user + }, options.hash || options); // check for "hash" prop if called as helper + + // Primarily useful for displaying a temporary image while uploading an image + if (options.uploading && !self.isUploaded()) { + return options.uploading; + } + + if (self.isMounted()) { + // See if we've stored in the requested store yet + var storeName = options.store || self.collection.primaryStore.name; + if (!self.hasStored(storeName)) { + if (options.storing) { + return options.storing; + } else if (!options.brokenIsFine) { + // In case we want to get back the url only when he is stored + if (Meteor.isServer && options.returnWhenStored) { + // Wait till file is stored to storeName + self.onStored(storeName); + } else { + // We want to return null if we know the URL will be a broken + // link because then we can avoid rendering broken links, broken + // images, etc. + return null; + } + } + } + + // Add filename to end of URL if we can determine one + var filename = options.filename || self.name({store: storeName}); + if (typeof filename === "string" && filename.length) { + filename = '/' + filename; + } else { + filename = ''; + } + + // TODO: Could we somehow figure out if the collection requires login? + var authToken = ''; + if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") { + if (options.auth !== false) { + // Add reactive deps on the user + Meteor.userId(); + + var authObject = { + authToken: Accounts._storedLoginToken() || '' + }; + + // If it's a number, we use that as the expiration time (in seconds) + if (options.auth === +options.auth) { + authObject.expiration = FS.HTTP.now() + options.auth * 1000; + } + + // Set the authToken + var authString = JSON.stringify(authObject); + authToken = FS.Utility.btoa(authString); + } + } else if (typeof options.auth === "string") { + // If the user supplies auth token the user will be responsible for + // updating + authToken = options.auth; + } + + // Construct query string + var params = {}; + if (authToken !== '') { + params.token = authToken; + } + if (options.download) { + params.download = true; + } + if (options.store) { + // We use options.store here instead of storeName because we want to omit the queryString + // whenever possible, allowing users to have "clean" URLs if they want. The server will + // assume the first store defined on the server, which means that we are assuming that + // the first on the client is also the first on the server. If that's not the case, the + // store option should be supplied. + params.store = options.store; + } + var queryString = FS.Utility.encodeParams(params); + if (queryString.length) { + queryString = '?' + queryString; + } + + // Determine which URL to use + var area; + if (options.metadata) { + area = '/record'; + } else { + area = '/files'; + } + + // Construct and return the http method url + return baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString; + } +}; + +/** + * @method FS.File.prototype.url Construct the file url + * @public + * @param {Object} [options] + * @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used. + * @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds. + * @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser. + * @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet. + * @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself. + * @param {String} [options.uploading=null] A URL to return while the file is being uploaded. + * @param {String} [options.storing=null] A URL to return while the file is being stored. + * @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store. + * + * Returns the HTTP URL for getting the file or its metadata. + */ +FS.File.prototype.url = function(options) { + self = this; + return rootUrlPathPrefix + self.urlRelative(options); +}; \ No newline at end of file diff --git a/packages/wekan-cfs-access-point/access-point-handlers.js b/packages/wekan-cfs-access-point/access-point-handlers.js new file mode 100644 index 000000000..e4615f069 --- /dev/null +++ b/packages/wekan-cfs-access-point/access-point-handlers.js @@ -0,0 +1,307 @@ +getHeaders = []; +getHeadersByCollection = {}; + +var contentDisposition = Npm.require('content-disposition'); + +FS.HTTP.Handlers = {}; + +/** + * @method FS.HTTP.Handlers.Del + * @public + * @returns {any} response + * + * HTTP DEL request handler + */ +FS.HTTP.Handlers.Del = function httpDelHandler(ref) { + var self = this; + var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); + + // If DELETE request, validate with 'remove' allow/deny, delete the file, and return + FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId); + + /* + * From the DELETE spec: + * A successful response SHOULD be 200 (OK) if the response includes an + * entity describing the status, 202 (Accepted) if the action has not + * yet been enacted, or 204 (No Content) if the action has been enacted + * but the response does not include an entity. + */ + self.setStatusCode(200); + + return { + deleted: !!ref.file.remove() + }; +}; + +/** + * @method FS.HTTP.Handlers.GetList + * @public + * @returns {Object} response + * + * HTTP GET file list request handler + */ +FS.HTTP.Handlers.GetList = function httpGetListHandler() { + // Not Yet Implemented + // Need to check publications and return file list based on + // what user is allowed to see +}; + +/* + requestRange will parse the range set in request header - if not possible it + will throw fitting errors and autofill range for both partial and full ranges + + throws error or returns the object: + { + start + end + length + unit + partial + } +*/ +var requestRange = function(req, fileSize) { + if (req) { + if (req.headers) { + var rangeString = req.headers.range; + + // Make sure range is a string + if (rangeString === ''+rangeString) { + + // range will be in the format "bytes=0-32767" + var parts = rangeString.split('='); + var unit = parts[0]; + + // Make sure parts consists of two strings and range is of type "byte" + if (parts.length == 2 && unit == 'bytes') { + // Parse the range + var range = parts[1].split('-'); + var start = Number(range[0]); + var end = Number(range[1]); + + // Fix invalid ranges? + if (range[0] != start) start = 0; + if (range[1] != end || !end) end = fileSize - 1; + + // Make sure range consists of a start and end point of numbers and start is less than end + if (start < end) { + + var partSize = 0 - start + end + 1; + + // Return the parsed range + return { + start: start, + end: end, + length: partSize, + size: fileSize, + unit: unit, + partial: (partSize < fileSize) + }; + + } else { + throw new Meteor.Error(416, "Requested Range Not Satisfiable"); + } + + } else { + // The first part should be bytes + throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable"); + } + + } else { + // No range found + } + + } else { + // throw new Error('No request headers set for _parseRange function'); + } + } else { + throw new Error('No request object passed to _parseRange function'); + } + + return { + start: 0, + end: fileSize - 1, + length: fileSize, + size: fileSize, + unit: 'bytes', + partial: false + }; +}; + +/** + * @method FS.HTTP.Handlers.Get + * @public + * @returns {any} response + * + * HTTP GET request handler + */ +FS.HTTP.Handlers.Get = function httpGetHandler(ref) { + var self = this; + // Once we have the file, we can test allow/deny validators + // XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access? + FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/); + + var storeName = ref.storeName; + + // If no storeName was specified, use the first defined storeName + if (typeof storeName !== "string") { + // No store handed, we default to primary store + storeName = ref.collection.primaryStore.name; + } + + // Get the storage reference + var storage = ref.collection.storesLookup[storeName]; + + if (!storage) { + throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"'); + } + + // Get the file + var copyInfo = ref.file.copies[storeName]; + + if (!copyInfo) { + throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store'); + } + + // Set the content type for file + if (typeof copyInfo.type === "string") { + self.setContentType(copyInfo.type); + } else { + self.setContentType('application/octet-stream'); + } + + // Add 'Content-Disposition' header if requested a download/attachment URL + if (typeof ref.download !== "undefined") { + var filename = ref.filename || copyInfo.name; + self.addHeader('Content-Disposition', contentDisposition(filename)); + } else { + self.addHeader('Content-Disposition', 'inline'); + } + + // Get the contents range from request + var range = requestRange(self.request, copyInfo.size); + + // Some browsers cope better if the content-range header is + // still included even for the full file being returned. + self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); + + // If a chunk/range was requested instead of the whole file, serve that' + if (range.partial) { + self.setStatusCode(206, 'Partial Content'); + } else { + self.setStatusCode(200, 'OK'); + } + + // Add any other global custom headers and collection-specific custom headers + FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) { + self.addHeader(header[0], header[1]); + }); + + // Inform clients about length (or chunk length in case of ranges) + self.addHeader('Content-Length', range.length); + + // Last modified header (updatedAt from file info) + self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString()); + + // Inform clients that we accept ranges for resumable chunked downloads + self.addHeader('Accept-Ranges', range.unit); + + if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); + + var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end}); + + readStream.on('error', function(err) { + // Send proper error message on get error + if (err.message && err.statusCode) { + self.Error(new Meteor.Error(err.statusCode, err.message)); + } else { + self.Error(new Meteor.Error(503, 'Service unavailable')); + } + }); + + readStream.pipe(self.createWriteStream()); +}; + +// File with unicode or other encodings filename can upload to server susscessfully, +// but when download, the HTTP header "Content-Disposition" cannot accept +// characters other than ASCII, the filename should be converted to binary or URI encoded. +// https://github.com/wekan/wekan/issues/784 +const originalHandler = FS.HTTP.Handlers.Get; +FS.HTTP.Handlers.Get = function (ref) { + try { + var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase(); + if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) { + ref.filename = encodeURIComponent(ref.filename); + } else if(userAgent.indexOf('firefox') >= 0) { + ref.filename = new Buffer(ref.filename).toString('binary'); + } else { + /* safari*/ + ref.filename = new Buffer(ref.filename).toString('binary'); + } + } catch (ex){ + ref.filename = ref.filename; + } + return originalHandler.call(this, ref); +}; + + +/** + * @method FS.HTTP.Handlers.PutInsert + * @public + * @returns {Object} response object with _id property + * + * HTTP PUT file insert request handler + */ +FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { + var self = this; + var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); + + FS.debug && console.log("HTTP PUT (insert) handler"); + + // Create the nice FS.File + var fileObj = new FS.File(); + + // Set its name + fileObj.name(opts.filename || null); + + // Attach the readstream as the file's data + fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'}); + + // Validate with insert allow/deny + FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId); + + // Insert file into collection, triggering readStream storage + ref.collection.insert(fileObj); + + // Send response + self.setStatusCode(200); + + // Return the new file id + return {_id: fileObj._id}; +}; + +/** + * @method FS.HTTP.Handlers.PutUpdate + * @public + * @returns {Object} response object with _id and chunk properties + * + * HTTP PUT file update chunk request handler + */ +FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { + var self = this; + var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); + + var chunk = parseInt(opts.chunk, 10); + if (isNaN(chunk)) chunk = 0; + + FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk); + + // Validate with insert allow/deny; also mounts and retrieves the file + FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId); + + self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) ); + + // Send response + self.setStatusCode(200); + + return { _id: ref.file._id, chunk: chunk }; +}; diff --git a/packages/wekan-cfs-access-point/access-point-server.js b/packages/wekan-cfs-access-point/access-point-server.js new file mode 100644 index 000000000..2f17cf9a0 --- /dev/null +++ b/packages/wekan-cfs-access-point/access-point-server.js @@ -0,0 +1,362 @@ +var path = Npm.require("path"); + +HTTP.publishFormats({ + fileRecordFormat: function (input) { + // Set the method scope content type to json + this.setContentType('application/json'); + if (FS.Utility.isArray(input)) { + return EJSON.stringify(FS.Utility.map(input, function (obj) { + return FS.Utility.cloneFileRecord(obj); + })); + } else { + return EJSON.stringify(FS.Utility.cloneFileRecord(input)); + } + } +}); + +/** + * @method FS.HTTP.setHeadersForGet + * @public + * @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value. + * @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections. + * @returns {undefined} + */ +FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { + if (typeof collections === "string") { + collections = [collections]; + } + if (collections) { + FS.Utility.each(collections, function(collectionName) { + getHeadersByCollection[collectionName] = headers || []; + }); + } else { + getHeaders = headers || []; + } +}; + +/** + * @method FS.HTTP.publish + * @public + * @param {FS.Collection} collection + * @param {Function} func - Publish function that returns a cursor. + * @returns {undefined} + * + * Publishes all documents returned by the cursor at a GET URL + * with the format baseUrl/record/collectionName. The publish + * function `this` is similar to normal `Meteor.publish`. + */ +FS.HTTP.publish = function fsHttpPublish(collection, func) { + var name = baseUrl + '/record/' + collection.name; + // Mount collection listing URL using http-publish package + HTTP.publish({ + name: name, + defaultFormat: 'fileRecordFormat', + collection: collection, + collectionGet: true, + collectionPost: false, + documentGet: true, + documentPut: false, + documentDelete: false + }, func); + + FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n'); +}; + +/** + * @method FS.HTTP.unpublish + * @public + * @param {FS.Collection} collection + * @returns {undefined} + * + * Unpublishes a restpoint created by a call to `FS.HTTP.publish` + */ +FS.HTTP.unpublish = function fsHttpUnpublish(collection) { + // Mount collection listing URL using http-publish package + HTTP.unpublish(baseUrl + '/record/' + collection.name); +}; + +_existingMountPoints = {}; + +/** + * @method defaultSelectorFunction + * @private + * @returns { collection, file } + * + * This is the default selector function + */ +var defaultSelectorFunction = function() { + var self = this; + // Selector function + // + // This function will have to return the collection and the + // file. If file not found undefined is returned - if null is returned the + // search was not possible + var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); + + // Get the collection name from the url + var collectionName = opts.collectionName; + + // Get the id from the url + var id = opts.id; + + // Get the collection + var collection = FS._collections[collectionName]; + + //if Mongo ObjectIds are used, then we need to use that in find statement + if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') { + // Get the file if possible else return null + var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null; + } else { + var file = (id && collection)? collection.findOne({ _id: id }): null; + } + + + // Return the collection and the file + return { + collection: collection, + file: file, + storeName: opts.store, + download: opts.download, + filename: opts.filename + }; +}; + +/* + * @method FS.HTTP.mount + * @public + * @param {array of string} mountPoints mount points to map rest functinality on + * @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with + * +*/ +FS.HTTP.mount = function(mountPoints, selector_f) { + // We take mount points as an array and we get a selector function + var selectorFunction = selector_f || defaultSelectorFunction; + + var accessPoint = { + 'stream': true, + 'auth': expirationAuth, + 'post': function(data) { + // Use the selector for finding the collection and file reference + var ref = selectorFunction.call(this); + + // We dont support post - this would be normal insert eg. of filerecord? + throw new Meteor.Error(501, "Not implemented", "Post is not supported"); + }, + 'put': function(data) { + // Use the selector for finding the collection and file reference + var ref = selectorFunction.call(this); + + // Make sure we have a collection reference + if (!ref.collection) + throw new Meteor.Error(404, "Not Found", "No collection found"); + + // Make sure we have a file reference + if (ref.file === null) { + // No id supplied so we will create a new FS.File instance and + // insert the supplied data. + return FS.HTTP.Handlers.PutInsert.apply(this, [ref]); + } else { + if (ref.file) { + return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]); + } else { + throw new Meteor.Error(404, "Not Found", 'No file found'); + } + } + }, + 'get': function(data) { + // Use the selector for finding the collection and file reference + var ref = selectorFunction.call(this); + + // Make sure we have a collection reference + if (!ref.collection) + throw new Meteor.Error(404, "Not Found", "No collection found"); + + // Make sure we have a file reference + if (ref.file === null) { + // No id supplied so we will return the published list of files ala + // http.publish in json format + return FS.HTTP.Handlers.GetList.apply(this, [ref]); + } else { + if (ref.file) { + return FS.HTTP.Handlers.Get.apply(this, [ref]); + } else { + throw new Meteor.Error(404, "Not Found", 'No file found'); + } + } + }, + 'delete': function(data) { + // Use the selector for finding the collection and file reference + var ref = selectorFunction.call(this); + + // Make sure we have a collection reference + if (!ref.collection) + throw new Meteor.Error(404, "Not Found", "No collection found"); + + // Make sure we have a file reference + if (ref.file) { + return FS.HTTP.Handlers.Del.apply(this, [ref]); + } else { + throw new Meteor.Error(404, "Not Found", 'No file found'); + } + } + }; + + var accessPoints = {}; + + // Add debug message + FS.debug && console.log('Registered HTTP method URLs:'); + + FS.Utility.each(mountPoints, function(mountPoint) { + // Couple mountpoint and accesspoint + accessPoints[mountPoint] = accessPoint; + // Remember our mountpoints + _existingMountPoints[mountPoint] = mountPoint; + // Add debug message + FS.debug && console.log(mountPoint); + }); + + // XXX: HTTP:methods should unmount existing mounts in case of overwriting? + HTTP.methods(accessPoints); + +}; + +/** + * @method FS.HTTP.unmount + * @public + * @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted + * + */ +FS.HTTP.unmount = function(mountPoints) { + // The mountPoints is optional, can be string or array if undefined then + // _existingMountPoints will be used + var unmountList; + // Container for the mount points to unmount + var unmountPoints = {}; + + if (typeof mountPoints === 'undefined') { + // Use existing mount points - unmount all + unmountList = _existingMountPoints; + } else if (mountPoints === ''+mountPoints) { + // Got a string + unmountList = [mountPoints]; + } else if (mountPoints.length) { + // Got an array + unmountList = mountPoints; + } + + // If we have a list to unmount + if (unmountList) { + // Iterate over each item + FS.Utility.each(unmountList, function(mountPoint) { + // Check _existingMountPoints to make sure the mount point exists in our + // context / was created by the FS.HTTP.mount + if (_existingMountPoints[mountPoint]) { + // Mark as unmount + unmountPoints[mountPoint] = false; + // Release + delete _existingMountPoints[mountPoint]; + } + }); + FS.debug && console.log('FS.HTTP.unmount:'); + FS.debug && console.log(unmountPoints); + // Complete unmount + HTTP.methods(unmountPoints); + } +}; + +// ### FS.Collection maps on HTTP pr. default on the following restpoints: +// * +// baseUrl + '/files/:collectionName/:id/:filename', +// baseUrl + '/files/:collectionName/:id', +// baseUrl + '/files/:collectionName' +// +// Change/ replace the existing mount point by: +// ```js +// // unmount all existing +// FS.HTTP.unmount(); +// // Create new mount point +// FS.HTTP.mount([ +// '/cfs/files/:collectionName/:id/:filename', +// '/cfs/files/:collectionName/:id', +// '/cfs/files/:collectionName' +// ]); +// ``` +// +mountUrls = function mountUrls() { + // We unmount first in case we are calling this a second time + FS.HTTP.unmount(); + + FS.HTTP.mount([ + baseUrl + '/files/:collectionName/:id/:filename', + baseUrl + '/files/:collectionName/:id', + baseUrl + '/files/:collectionName' + ]); +}; + +// Returns the userId from URL token +var expirationAuth = function expirationAuth() { + var self = this; + + // Read the token from '/hello?token=base64' + var encodedToken = self.query.token; + + FS.debug && console.log("token: "+encodedToken); + + if (!encodedToken || !Meteor.users) return false; + + // Check the userToken before adding it to the db query + // Set the this.userId + var tokenString = FS.Utility.atob(encodedToken); + + var tokenObject; + try { + tokenObject = JSON.parse(tokenString); + } catch(err) { + throw new Meteor.Error(400, 'Bad Request'); + } + + // XXX: Do some check here of the object + var userToken = tokenObject.authToken; + if (userToken !== ''+userToken) { + throw new Meteor.Error(400, 'Bad Request'); + } + + // If we have an expiration token we should check that it's still valid + if (tokenObject.expiration != null) { + // check if its too old + var now = Date.now(); + if (tokenObject.expiration < now) { + FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now); + throw new Meteor.Error(500, 'Expired token'); + } + } + + // We are not on a secure line - so we have to look up the user... + var user = Meteor.users.findOne({ + $or: [ + {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)}, + {'services.resume.loginTokens.token': userToken} + ] + }); + + // Set the userId in the scope + return user && user._id; +}; + +HTTP.methods( + {'/cfs/servertime': { + get: function(data) { + return Date.now().toString(); + } + } +}); + +// Unify client / server api +FS.HTTP.now = function() { + return Date.now(); +}; + +// Start up the basic mount points +Meteor.startup(function () { + mountUrls(); +}); diff --git a/packages/wekan-cfs-access-point/api.md b/packages/wekan-cfs-access-point/api.md new file mode 100644 index 000000000..18a92c7a3 --- /dev/null +++ b/packages/wekan-cfs-access-point/api.md @@ -0,0 +1,271 @@ +## wekan-cfs-access-point Public API ## + +CollectionFS, add ddp and http accesspoint capability + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +- + +###
    *FSHTTP*.setBaseUrl(newBaseUrl)  Anywhere ### + +*This method __setBaseUrl__ is defined in `FS.HTTP`* + +__Arguments__ + +* __newBaseUrl__ *{String}* + + Change the base URL for the HTTP GET and DELETE endpoints. + + +__Returns__ *{undefined}* + + +> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29) + + +- + +### *fsFile*.url([options])  Anywhere ### + +*This method __url__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{Object}* (Optional) + * __store__ *{String}* (Optional) + + Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used. + + * __auth__ *{Boolean}* (Optional, Default = null) + + Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds. + + * __download__ *{Boolean}* (Optional, Default = false) + + Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser. + + * __brokenIsFine__ *{Boolean}* (Optional, Default = false) + + Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet. + + * __metadata__ *{Boolean}* (Optional, Default = false) + + Return the URL for the file metadata access point rather than the file itself. + + * __uploading__ *{String}* (Optional, Default = null) + + A URL to return while the file is being uploaded. + + * __storing__ *{String}* (Optional, Default = null) + + A URL to return while the file is being stored. + + * __filename__ *{String}* (Optional, Default = null) + + Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store. + + + +Returns the HTTP URL for getting the file or its metadata. + +> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72) + + +- + +### *FSHTTPHandlers*.Del()  Server ### + +*This method __Del__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{any}* +response + + +HTTP DEL request handler + +> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13) + + +- + +### *FSHTTPHandlers*.GetList()  Server ### + +*This method __GetList__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response + + +HTTP GET file list request handler + +> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41) + + +- + +### *FSHTTPHandlers*.Get()  Server ### + +*This method __Get__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{any}* +response + + +HTTP GET request handler + +> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135) + + +- + +### *FSHTTPHandlers*.PutInsert()  Server ### + +*This method __PutInsert__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response object with _id property + + +HTTP PUT file insert request handler + +> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229) + + +- + +### *FSHTTPHandlers*.PutUpdate()  Server ### + +*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response object with _id and chunk properties + + +HTTP PUT file update chunk request handler + +> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264) + + +- + +### *FSHTTP*.setHeadersForGet(headers, [collections])  Server ### + +*This method __setHeadersForGet__ is defined in `FS.HTTP`* + +__Arguments__ + +* __headers__ *{Array}* + + List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value. + +* __collections__ *{Array|String}* (Optional) + + Which collections the headers should be added for. Omit this argument to add the header for all collections. + + +__Returns__ *{undefined}* + + +> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24) + + +- + +### *FSHTTP*.publish(collection, func)  Server ### + +*This method __publish__ is defined in `FS.HTTP`* + +__Arguments__ + +* __collection__ *{[FS.Collection](#FS.Collection)}* +* __func__ *{Function}* + + Publish function that returns a cursor. + + +__Returns__ *{undefined}* + + +Publishes all documents returned by the cursor at a GET URL +with the format baseUrl/record/collectionName. The publish +function `this` is similar to normal `Meteor.publish`. + +> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48) + + +- + +### *FSHTTP*.unpublish(collection)  Server ### + +*This method __unpublish__ is defined in `FS.HTTP`* + +__Arguments__ + +* __collection__ *{[FS.Collection](#FS.Collection)}* + +__Returns__ *{undefined}* + + +Unpublishes a restpoint created by a call to `FS.HTTP.publish` + +> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73) + + +- + +### *FSHTTP*.mount(mountPoints, selector_f)  Server ### + +*This method __mount__ is defined in `FS.HTTP`* + +__Arguments__ + +* __mountPoints__ *{[array of string](#array of string)}* + + mount points to map rest functinality on + +* __selector_f__ *{function}* + + [selector] function returns `{ collection, file }` for mount points to work with + + + +> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125) + + +- + +### *FSHTTP*.unmount([mountPoints])  Server ### + +*This method __unmount__ is defined in `FS.HTTP`* + +__Arguments__ + +* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional) + + Optional, if not specified all mountpoints are unmounted + + + + +> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223) + + + +- +### FS.Collection maps on HTTP pr. default on the following restpoints: +* +baseUrl + '/files/:collectionName/:id/:filename', +baseUrl + '/files/:collectionName/:id', +baseUrl + '/files/:collectionName' + +Change/ replace the existing mount point by: +```js +unmount all existing +FS.HTTP.unmount(); +Create new mount point +FS.HTTP.mount([ +'/cfs/files/:collectionName/:id/:filename', +'/cfs/files/:collectionName/:id', +'/cfs/files/:collectionName' +]); +``` diff --git a/packages/wekan-cfs-access-point/internal.api.md b/packages/wekan-cfs-access-point/internal.api.md new file mode 100644 index 000000000..a7bf6477e --- /dev/null +++ b/packages/wekan-cfs-access-point/internal.api.md @@ -0,0 +1,332 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["access-point-common.js"](access-point-common.js) Where: {server|client}__ + +*** + +### *FSHTTP*.setBaseUrl(newBaseUrl)  Anywhere ### + +*This method __setBaseUrl__ is defined in `FS.HTTP`* + +__Arguments__ + +* __newBaseUrl__ *{String}* + + Change the base URL for the HTTP GET and DELETE endpoints. + + +__Returns__ *{undefined}* + + +> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29) + + +- + +### *fsFile*.url([options])  Anywhere ### + +*This method __url__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{Object}* (Optional) + * __store__ *{String}* (Optional) + + Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used. + + * __auth__ *{Boolean}* (Optional, Default = null) + + Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds. + + * __download__ *{Boolean}* (Optional, Default = false) + + Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser. + + * __brokenIsFine__ *{Boolean}* (Optional, Default = false) + + Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet. + + * __metadata__ *{Boolean}* (Optional, Default = false) + + Return the URL for the file metadata access point rather than the file itself. + + * __uploading__ *{String}* (Optional, Default = null) + + A URL to return while the file is being uploaded. + + * __storing__ *{String}* (Optional, Default = null) + + A URL to return while the file is being stored. + + * __filename__ *{String}* (Optional, Default = null) + + Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store. + + + +Returns the HTTP URL for getting the file or its metadata. + +> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72) + + +*** + +__File: ["access-point-handlers.js"](access-point-handlers.js) Where: {server}__ + +*** + +### *FSHTTPHandlers*.Del()  Server ### + +*This method __Del__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{any}* +response + + +HTTP DEL request handler + +> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13) + + +- + +### *self*.setStatusCode {any}  Server ### + +``` +From the DELETE spec: +A successful response SHOULD be 200 (OK) if the response includes an +entity describing the status, 202 (Accepted) if the action has not +yet been enacted, or 204 (No Content) if the action has been enacted +but the response does not include an entity. +``` +*This property __setStatusCode__ is defined in `self`* + +> ```self.setStatusCode(200);``` [access-point-handlers.js:27](access-point-handlers.js#L27) + + +- + +### *FSHTTPHandlers*.GetList()  Server ### + +*This method __GetList__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response + + +HTTP GET file list request handler + +> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41) + + +- + +### requestRange {any}  Server ### + +``` +requestRange will parse the range set in request header - if not possible it +will throw fitting errors and autofill range for both partial and full ranges +throws error or returns the object: +{ +start +end +length +unit +partial +} +``` +*This property is private* + +> ```var requestRange = function(req, fileSize) { ...``` [access-point-handlers.js:60](access-point-handlers.js#L60) + + +- + +### *FSHTTPHandlers*.Get()  Server ### + +*This method __Get__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{any}* +response + + +HTTP GET request handler + +> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135) + + +- + +### *FSHTTPHandlers*.PutInsert()  Server ### + +*This method __PutInsert__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response object with _id property + + +HTTP PUT file insert request handler + +> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229) + + +- + +### *FSHTTPHandlers*.PutUpdate()  Server ### + +*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`* + +__Returns__ *{Object}* +response object with _id and chunk properties + + +HTTP PUT file update chunk request handler + +> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264) + + +*** + +__File: ["access-point-server.js"](access-point-server.js) Where: {server}__ + +*** + +### *FSHTTP*.setHeadersForGet(headers, [collections])  Server ### + +*This method __setHeadersForGet__ is defined in `FS.HTTP`* + +__Arguments__ + +* __headers__ *{Array}* + + List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value. + +* __collections__ *{Array|String}* (Optional) + + Which collections the headers should be added for. Omit this argument to add the header for all collections. + + +__Returns__ *{undefined}* + + +> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24) + + +- + +### *FSHTTP*.publish(collection, func)  Server ### + +*This method __publish__ is defined in `FS.HTTP`* + +__Arguments__ + +* __collection__ *{[FS.Collection](#FS.Collection)}* +* __func__ *{Function}* + + Publish function that returns a cursor. + + +__Returns__ *{undefined}* + + +Publishes all documents returned by the cursor at a GET URL +with the format baseUrl/record/collectionName. The publish +function `this` is similar to normal `Meteor.publish`. + +> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48) + + +- + +### *FSHTTP*.unpublish(collection)  Server ### + +*This method __unpublish__ is defined in `FS.HTTP`* + +__Arguments__ + +* __collection__ *{[FS.Collection](#FS.Collection)}* + +__Returns__ *{undefined}* + + +Unpublishes a restpoint created by a call to `FS.HTTP.publish` + +> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73) + + +- + +### defaultSelectorFunction()  Server ### + +*This method is private* + +__Returns__ *{ collection, file }* + + +This is the default selector function + +> ```var defaultSelectorFunction = function() { ...``` [access-point-server.js:87](access-point-server.js#L87) + + +- + +### *FSHTTP*.mount(mountPoints, selector_f)  Server ### + +*This method __mount__ is defined in `FS.HTTP`* + +__Arguments__ + +* __mountPoints__ *{[array of string](#array of string)}* + + mount points to map rest functinality on + +* __selector_f__ *{function}* + + [selector] function returns `{ collection, file }` for mount points to work with + + + +> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125) + + +- + +### *FSHTTP*.unmount([mountPoints])  Server ### + +*This method __unmount__ is defined in `FS.HTTP`* + +__Arguments__ + +* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional) + + Optional, if not specified all mountpoints are unmounted + + + + +> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223) + + + +- +### FS.Collection maps on HTTP pr. default on the following restpoints: +* +baseUrl + '/files/:collectionName/:id/:filename', +baseUrl + '/files/:collectionName/:id', +baseUrl + '/files/:collectionName' + +Change/ replace the existing mount point by: +```js +unmount all existing +FS.HTTP.unmount(); +Create new mount point +FS.HTTP.mount([ +'/cfs/files/:collectionName/:id/:filename', +'/cfs/files/:collectionName/:id', +'/cfs/files/:collectionName' +]); +``` diff --git a/packages/wekan-cfs-access-point/package.js b/packages/wekan-cfs-access-point/package.js new file mode 100644 index 000000000..913252e2a --- /dev/null +++ b/packages/wekan-cfs-access-point/package.js @@ -0,0 +1,65 @@ +Package.describe({ + name: 'wekan-cfs-access-point', + version: '0.1.50', + summary: 'CollectionFS, add ddp and http accesspoint capability', + git: 'https://github.com/zcfs/Meteor-cfs-access-point.git' +}); + +Npm.depends({ + "content-disposition": "0.5.0" +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + // This imply is needed for tests, and is technically probably correct anyway. + api.imply([ + 'wekan-cfs-base-package' + ]); + + api.use([ + //CFS packages + 'wekan-cfs-base-package@0.0.30', + 'wekan-cfs-file@0.1.16', + //Core packages + 'check', + 'ejson', + //Other packages + 'wekan-cfs-http-methods@0.0.29', + 'wekan-cfs-http-publish@0.0.13' + ]); + + api.addFiles([ + 'access-point-common.js', + 'access-point-handlers.js', + 'access-point-server.js' + ], 'server'); + + api.addFiles([ + 'access-point-common.js', + 'access-point-client.js' + ], 'client'); +}); + +Package.onTest(function (api) { + api.versionsFrom('1.0'); + + api.use([ + //CFS packages + 'wekan-cfs-access-point', + 'wekan-cfs-standard-packages@0.0.2', + 'wekan-cfs-gridfs@0.0.0', + //Core packages + 'test-helpers', + 'http', + 'tinytest', + 'underscore', + 'ejson', + 'ordered-dict', + 'random', + 'deps' + ]); + + api.addFiles('tests/client-tests.js', 'client'); + api.addFiles('tests/server-tests.js', 'server'); +}); diff --git a/packages/wekan-cfs-access-point/tests/client-tests.js b/packages/wekan-cfs-access-point/tests/client-tests.js new file mode 100644 index 000000000..a0148ec17 --- /dev/null +++ b/packages/wekan-cfs-access-point/tests/client-tests.js @@ -0,0 +1,125 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-access-point - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP'); +}); + +Images = new FS.Collection('images', { + stores: [ + new FS.Store.GridFS('gridList') + ] +}); + +Meteor.subscribe("img"); + +var id; + +Tinytest.addAsync('cfs-access-point - client - addTestImage', function(test, onComplete) { + Meteor.call('addTestImage', function(err, result) { + id = result; + test.equal(typeof id, "string", "Test image was not inserted properly"); + //Don't continue until the data has been stored + Deps.autorun(function (c) { + var img = Images.findOne(id); + if (img && img.hasCopy('gridList')) { + onComplete(); + c.stop(); + } + }); + }); +}); + +Tinytest.addAsync('cfs-access-point - client - GET list of files in collection', function(test, onComplete) { + + HTTP.get(Meteor.absoluteUrl('cfs/record/images'), function(err, result) { + // Test the length of array result + var len = result.data && result.data.length; + test.isTrue(!!len, 'Result was empty'); + // Get the object + var obj = result.data && result.data[0] || {}; + test.equal(obj._id, id, 'Didn\'t get the expected result'); + onComplete(); + }); + +}); + +Tinytest.addAsync('cfs-access-point - client - GET filerecord', function(test, onComplete) { + + HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) { + // Get the object + var obj = result.data; + test.equal(typeof obj, "object", "Expected object data"); + test.equal(obj._id, id, 'Didn\'t get the expected result'); + onComplete(); + }); + +}); + +Tinytest.addAsync('cfs-access-point - client - GET file itself', function(test, onComplete) { + + HTTP.get(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) { + test.isTrue(!!result.content, "Expected content in response"); + console.log(result); + test.equal(result.statusCode, 200, "Expected 200 OK response"); + onComplete(); + }); + +}); + +Tinytest.addAsync('cfs-access-point - client - PUT new file data (update)', function(test, onComplete) { +// TODO +// HTTP.put(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) { +// test.equal(result.statusCode, 200, "Expected 200 OK response"); + onComplete(); +// }); + +}); + +Tinytest.addAsync('cfs-access-point - client - PUT insert a new file', function(test, onComplete) { +// TODO +// HTTP.put(Meteor.absoluteUrl('cfs/files/images'), function(err, result) { +// test.equal(result.statusCode, 200, "Expected 200 OK response"); + onComplete(); +// }); + +}); + +Tinytest.addAsync('cfs-access-point - client - DELETE filerecord and data', function(test, onComplete) { + + HTTP.del(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) { + test.equal(result.statusCode, 200, "Expected 200 OK response"); + + // Make sure it's gone + HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) { + test.isTrue(!!err, 'Expected 404 error'); + test.equal(result.statusCode, 404, "Expected 404 response"); + onComplete(); + }); + }); + +}); + +//TODO test FS.File.prototype.url method with various options + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-access-point/tests/server-tests.js b/packages/wekan-cfs-access-point/tests/server-tests.js new file mode 100644 index 000000000..4a23f1cb6 --- /dev/null +++ b/packages/wekan-cfs-access-point/tests/server-tests.js @@ -0,0 +1,68 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +FS.debug = true; + +Tinytest.add('cfs-access-point - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP'); +}); + +Images = new FS.Collection('images', { + stores: [ + new FS.Store.GridFS('gridList') + ] +}); + +Images.allow({ + insert: function() { + return true; + }, + update: function() { + return true; + }, + remove: function() { + return true; + }, + download: function() { + return true; + } +}); + +Meteor.publish("img", function () { + return Images.find(); +}); + +FS.HTTP.publish(Images, function () { + return Images.find(); +}); + +Meteor.methods({ + addTestImage: function() { + Images.remove({}); + var url = "http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg"; + var fsFile = Images.insert(url); + return fsFile._id; + } +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-base-package/.travis.yml b/packages/wekan-cfs-base-package/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-base-package/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-base-package/LICENSE.md b/packages/wekan-cfs-base-package/LICENSE.md new file mode 100644 index 000000000..51e60c3d0 --- /dev/null +++ b/packages/wekan-cfs-base-package/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-base-package/README.md b/packages/wekan-cfs-base-package/README.md new file mode 100644 index 000000000..235ca9aa0 --- /dev/null +++ b/packages/wekan-cfs-base-package/README.md @@ -0,0 +1,11 @@ +wekan-cfs-base-package +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. + +This package provides the `FS` namespace and helper methods used by many +CollectionFS packages. \ No newline at end of file diff --git a/packages/wekan-cfs-base-package/api.md b/packages/wekan-cfs-base-package/api.md new file mode 100644 index 000000000..cb7667945 --- /dev/null +++ b/packages/wekan-cfs-base-package/api.md @@ -0,0 +1,213 @@ +## cfs-base-package Public API ## + +CollectionFS, Base package + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +############################################################################# + +HELPERS + +############################################################################# +- + +### *fsUtility*.cloneFileRecord(rec, [options])  Anywhere ### + +*This method __cloneFileRecord__ is defined in `FS.Utility`* + +__Arguments__ + +* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}* +* __options__ *{Object}* (Optional) + * __full__ *{Boolean}* (Optional, Default = false) + + Set `true` to prevent certain properties from being omitted from the clone. + + +__Returns__ *{Object}* +Cloned filerecord + + +Makes a shallow clone of `rec`, filtering out some properties that might be present if +it's an FS.File instance, but which we never want to be part of the stored +filerecord. + +This is a blacklist clone rather than a whitelist because we want the user to be able +to specify whatever additional properties they wish. + +In general, we expect the following whitelist properties used by the internal and +external APIs: + +_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt + +Those properties, and any additional properties added by the user, should be present +in the returned object, which is suitable for inserting into the backing collection or +extending an FS.File instance. + + +> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71) + + +- + +### *fsUtility*.defaultCallback([err])  Anywhere ### + +*This method __defaultCallback__ is defined in `FS.Utility`* + +__Arguments__ + +* __err__ *{[Error](#Error)}* (Optional) + +__Returns__ *{undefined}* + + +Can be used as a default callback for client methods that need a callback. +Simply throws the provided error if there is one. + +> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96) + + +- + +### *fsUtility*.defaultCallback([f], [err])  Anywhere ### + +*This method __defaultCallback__ is defined in `FS.Utility`* + +__Arguments__ + +* __f__ *{Function}* (Optional) + + A callback function, if you have one. Can be undefined or null. + +* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional) + + Error or error message (string) + + +__Returns__ *{Any}* +the callback result if any + + +Handle Error, creates an Error instance with the given text. If callback is +a function, passes the error to that function. Otherwise throws it. Useful +for dealing with errors in methods that optionally accept a callback. + +> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120) + + +- + +### *fsUtility*.noop()  Anywhere ### + +*This method __noop__ is defined in `FS.Utility`* + +Use this to hand a no operation / empty function + +> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134) + + +- + +### *fsUtility*.getFileExtension(name)  Anywhere ### + +*This method __getFileExtension__ is defined in `FS.Utility`* + +__Arguments__ + +* __name__ *{String}* + + A filename, filepath, or URL that may or may not have an extension. + + +__Returns__ *{String}* +The extension or an empty string if no extension found. + + +> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205) + + +- + +### *fsUtility*.setFileExtension(name, ext)  Anywhere ### + +*This method __setFileExtension__ is defined in `FS.Utility`* + +__Arguments__ + +* __name__ *{String}* + + A filename that may or may not already have an extension. + +* __ext__ *{String}* + + An extension without leading period, which you want to be the new extension on `name`. + + +__Returns__ *{String}* +The filename with changed extension. + + +> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222) + + +- + +### *fsUtility*.binaryToBuffer(data)  Server ### + +*This method __binaryToBuffer__ is defined in `FS.Utility`* + +__Arguments__ + +* __data__ *{Uint8Array}* + +__Returns__ *{Buffer}* + + +Converts a Uint8Array instance to a Node Buffer instance + +> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9) + + +- + +### *fsUtility*.bufferToBinary(data)  Server ### + +*This method __bufferToBinary__ is defined in `FS.Utility`* + +__Arguments__ + +* __data__ *{Buffer}* + +__Returns__ *{Uint8Array}* + + +Converts a Node Buffer instance to a Uint8Array instance + +> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26) + + +- + +### *fsUtility*.eachFile(e, f)  Client ### + +*This method __eachFile__ is defined in `FS.Utility`* + +__Arguments__ + +* __e__ *{[Event](#Event)}* + + Browser event + +* __f__ *{Function}* + + Function to run for each file found in the event. + + +__Returns__ *{undefined}* + + +Utility for iteration over files in event + +> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37) + + diff --git a/packages/wekan-cfs-base-package/base-client.js b/packages/wekan-cfs-base-package/base-client.js new file mode 100644 index 000000000..4d0d2ff59 --- /dev/null +++ b/packages/wekan-cfs-base-package/base-client.js @@ -0,0 +1,51 @@ + +//XXX not sure this is still working properly? +FS.Utility.connectionLogin = function(connection) { + // We check if the accounts package is installed, since we depend on + // `Meteor.userId()` + if (typeof Accounts !== 'undefined') { + // Monitor logout from main connection + Meteor.startup(function() { + Tracker.autorun(function() { + var userId = Meteor.userId(); + if (userId) { + connection.onReconnect = function() { + var token = Accounts._storedLoginToken(); + connection.apply('login', [{resume: token}], function(err, result) { + if (!err && result) { + connection.setUserId(result.id); + } + }); + }; + } else { + connection.onReconnect = null; + connection.setUserId(null); + } + }); + }); + + } +}; + +/** + * @method FS.Utility.eachFile + * @public + * @param {Event} e - Browser event + * @param {Function} f - Function to run for each file found in the event. + * @returns {undefined} + * + * Utility for iteration over files in event + */ +FS.Utility.eachFile = function(e, f) { + var evt = (e.originalEvent || e); + + var files = evt.target.files; + + if (!files || files.length === 0) { + files = evt.dataTransfer ? evt.dataTransfer.files : []; + } + + for (var i = 0; i < files.length; i++) { + f(files[i], i); + } +}; diff --git a/packages/wekan-cfs-base-package/base-common.js b/packages/wekan-cfs-base-package/base-common.js new file mode 100644 index 000000000..082621e7b --- /dev/null +++ b/packages/wekan-cfs-base-package/base-common.js @@ -0,0 +1,317 @@ +// Exported namespace +FS = {}; + +// namespace for adapters; XXX should this be added by cfs-storage-adapter pkg instead? +FS.Store = { + GridFS: function () { + throw new Error('To use FS.Store.GridFS, you must add the "wekan-cfs-gridfs" package.'); + }, + FileSystem: function () { + throw new Error('To use FS.Store.FileSystem, you must add the "wekan-cfs-filesystem" package.'); + }, + S3: function () { + throw new Error('To use FS.Store.S3, you must add the "wekan-cfs-s3" package.'); + }, + WABS: function () { + throw new Error('To use FS.Store.WABS, you must add the "wekan-cfs-wabs" package.'); + }, + Dropbox: function () { + throw new Error('To use FS.Store.Dropbox, you must add the "wekan-cfs-dropbox" package.'); + } +}; + +// namespace for access points +FS.AccessPoint = {}; + +// namespace for utillities +FS.Utility = {}; + +// A general place for any package to store global config settings +FS.config = {}; + +// An internal collection reference +FS._collections = {}; + +// Test scope +_Utility = {}; + +// ############################################################################# +// +// HELPERS +// +// ############################################################################# + +/** @method _Utility.defaultZero + * @private + * @param {Any} val Returns number or 0 if value is a falsy + */ +_Utility.defaultZero = function(val) { + return +(val || 0); +}; + +/** + * @method FS.Utility.cloneFileRecord + * @public + * @param {FS.File|FS.Collection filerecord} rec + * @param {Object} [options] + * @param {Boolean} [options.full=false] Set `true` to prevent certain properties from being omitted from the clone. + * @returns {Object} Cloned filerecord + * + * Makes a shallow clone of `rec`, filtering out some properties that might be present if + * it's an FS.File instance, but which we never want to be part of the stored + * filerecord. + * + * This is a blacklist clone rather than a whitelist because we want the user to be able + * to specify whatever additional properties they wish. + * + * In general, we expect the following whitelist properties used by the internal and + * external APIs: + * + * _id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt + * + * Those properties, and any additional properties added by the user, should be present + * in the returned object, which is suitable for inserting into the backing collection or + * extending an FS.File instance. + * + */ +FS.Utility.cloneFileRecord = function(rec, options) { + options = options || {}; + var result = {}; + // We use this method for two purposes. If using it to clone one FS.File into another, then + // we want a full clone. But if using it to get a filerecord object for inserting into the + // internal collection, then there are certain properties we want to omit so that they aren't + // stored in the collection. + var omit = options.full ? [] : ['collectionName', 'collection', 'data', 'createdByTransform']; + for (var prop in rec) { + if (rec.hasOwnProperty(prop) && !_.contains(omit, prop)) { + result[prop] = rec[prop]; + } + } + return result; +}; + +/** + * @method FS.Utility.defaultCallback + * @public + * @param {Error} [err] + * @returns {undefined} + * + * Can be used as a default callback for client methods that need a callback. + * Simply throws the provided error if there is one. + */ +FS.Utility.defaultCallback = function defaultCallback(err) { + if (err) { + // Show gentle error if Meteor error + if (err instanceof Meteor.Error) { + console.error(err.message); + } else { + // Normal error, just throw error + throw err; + } + + } +}; + +/** + * @method FS.Utility.defaultCallback + * @public + * @param {Function} [f] A callback function, if you have one. Can be undefined or null. + * @param {Meteor.Error | Error | String} [err] Error or error message (string) + * @returns {Any} the callback result if any + * + * Handle Error, creates an Error instance with the given text. If callback is + * a function, passes the error to that function. Otherwise throws it. Useful + * for dealing with errors in methods that optionally accept a callback. + */ +FS.Utility.handleError = function(f, err, result) { + // Set callback + var callback = (typeof f === 'function')? f : FS.Utility.defaultCallback; + // Set the err + var error = (err === ''+err)? new Error(err) : err; + // callback + return callback(error, result); +} + +/** + * @method FS.Utility.noop + * @public + * Use this to hand a no operation / empty function + */ +FS.Utility.noop = function() {}; + +/** + * @method validateAction + * @private + * @param {Object} validators - The validators object to use, with `deny` and `allow` properties. + * @param {FS.File} fileObj - Mounted or mountable file object to be passed to validators. + * @param {String} userId - The ID of the user who is attempting the action. + * @returns {undefined} + * + * Throws a "400-Bad Request" Meteor error if the file is not mounted or + * a "400-Access denied" Meteor error if the action is not allowed. + */ +FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { + var denyValidators = validators.deny; + var allowValidators = validators.allow; + + // If insecure package is used and there are no validators defined, + // allow the action. + if (typeof Package === 'object' + && Package.insecure + && denyValidators.length + allowValidators.length === 0) { + return; + } + + // If already mounted, validators should receive a fileObj + // that is fully populated + if (fileObj.isMounted()) { + fileObj.getFileRecord(); + } + + // Any deny returns true means denied. + if (_.any(denyValidators, function(validator) { + return validator(userId, fileObj); + })) { + throw new Meteor.Error(403, "Access denied"); + } + // Any allow returns true means proceed. Throw error if they all fail. + if (_.all(allowValidators, function(validator) { + return !validator(userId, fileObj); + })) { + throw new Meteor.Error(403, "Access denied"); + } +}; + +/** + * @method FS.Utility.getFileName + * @private + * @param {String} name - A filename, filepath, or URL + * @returns {String} The filename without the URL, filepath, or query string + */ +FS.Utility.getFileName = function utilGetFileName(name) { + // in case it's a URL, strip off potential query string + // should have no effect on filepath + name = name.split('?')[0]; + // strip off beginning path or url + var lastSlash = name.lastIndexOf('/'); + if (lastSlash !== -1) { + name = name.slice(lastSlash + 1); + } + return name; +}; + +/** + * @method FS.Utility.getFileExtension + * @public + * @param {String} name - A filename, filepath, or URL that may or may not have an extension. + * @returns {String} The extension or an empty string if no extension found. + */ +FS.Utility.getFileExtension = function utilGetFileExtension(name) { + name = FS.Utility.getFileName(name); + // Seekout the last '.' if found + var found = name.lastIndexOf('.'); + // Return the extension if found else '' + // If found is -1, we return '' because there is no extension + // If found is 0, we return '' because it's a hidden file + return (found > 0 ? name.slice(found + 1).toLowerCase() : ''); +}; + +/** + * @method FS.Utility.setFileExtension + * @public + * @param {String} name - A filename that may or may not already have an extension. + * @param {String} ext - An extension without leading period, which you want to be the new extension on `name`. + * @returns {String} The filename with changed extension. + */ +FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { + if (!name || !name.length) { + return name; + } + var currentExt = FS.Utility.getFileExtension(name); + if (currentExt.length) { + name = name.slice(0, currentExt.length * -1) + ext; + } else { + name = name + '.' + ext; + } + return name; +}; + +/* + * Borrowed these from http package + */ +FS.Utility.encodeParams = function encodeParams(params) { + var buf = []; + _.each(params, function(value, key) { + if (buf.length) + buf.push('&'); + buf.push(FS.Utility.encodeString(key), '=', FS.Utility.encodeString(value)); + }); + return buf.join('').replace(/%20/g, '+'); +}; + +FS.Utility.encodeString = function encodeString(str) { + return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); +}; + +/* + * btoa and atob shims for client and server + */ + +FS.Utility._btoa = function _fsUtility_btoa(str) { + var buffer; + + if (str instanceof Buffer) { + buffer = str; + } else { + buffer = new Buffer(str.toString(), 'binary'); + } + + return buffer.toString('base64'); +}; + +FS.Utility.btoa = function fsUtility_btoa(str) { + if (typeof btoa === 'function') { + // Client + return btoa(str); + } else if (typeof Buffer !== 'undefined') { + // Server + return FS.Utility._btoa(str); + } else { + throw new Error('FS.Utility.btoa: Cannot base64 encode on your system'); + } +}; + +FS.Utility._atob = function _fsUtility_atob(str) { + return new Buffer(str, 'base64').toString('binary'); +}; + +FS.Utility.atob = function fsUtility_atob(str) { + if (typeof atob === 'function') { + // Client + return atob(str); + } else if (typeof Buffer !== 'undefined') { + // Server + return FS.Utility._atob(str); + } else { + throw new Error('FS.Utility.atob: Cannot base64 encode on your system'); + } +}; + +// Api wrap for 3party libs like underscore +FS.Utility.extend = _.extend; + +FS.Utility.each = _.each; + +FS.Utility.isEmpty = _.isEmpty; + +FS.Utility.indexOf = _.indexOf; + +FS.Utility.isArray = _.isArray; + +FS.Utility.map = _.map; + +FS.Utility.once = _.once; + +FS.Utility.include = _.include; + +FS.Utility.size = _.size; diff --git a/packages/wekan-cfs-base-package/base-server.js b/packages/wekan-cfs-base-package/base-server.js new file mode 100644 index 000000000..7b9309b7d --- /dev/null +++ b/packages/wekan-cfs-base-package/base-server.js @@ -0,0 +1,95 @@ +/** + * @method FS.Utility.binaryToBuffer + * @public + * @param {Uint8Array} data + * @returns {Buffer} + * + * Converts a Uint8Array instance to a Node Buffer instance + */ +FS.Utility.binaryToBuffer = function(data) { + var len = data.length; + var buffer = new Buffer(len); + for (var i = 0; i < len; i++) { + buffer[i] = data[i]; + } + return buffer; +}; + +/** + * @method FS.Utility.bufferToBinary + * @public + * @param {Buffer} data + * @returns {Uint8Array} + * + * Converts a Node Buffer instance to a Uint8Array instance + */ +FS.Utility.bufferToBinary = function(data) { + var len = data.length; + var binary = EJSON.newBinary(len); + for (var i = 0; i < len; i++) { + binary[i] = data[i]; + } + return binary; +}; + +/** + * @method FS.Utility.safeCallback + * @public + * @param {Function} callback + * @returns {Function} + * + * Makes a callback safe for Meteor code + */ +FS.Utility.safeCallback = function (callback) { + return Meteor.bindEnvironment(callback, function(err) { throw err; }); +}; + +/** + * @method FS.Utility.safeStream + * @public + * @param {Stream} nodestream + * @returns {Stream} + * + * Adds `safeOn` and `safeOnce` methods to a NodeJS Stream + * object. These are the same as `on` and `once`, except + * that the callback is wrapped for use in Meteor. + */ +FS.Utility.safeStream = function(nodestream) { + if (!nodestream || typeof nodestream.on !== 'function') + throw new Error('FS.Utility.safeStream requires a NodeJS Stream'); + + // Create Meteor safe events + nodestream.safeOn = function(name, callback) { + return nodestream.on(name, FS.Utility.safeCallback(callback)); + }; + + // Create Meteor safe events + nodestream.safeOnce = function(name, callback) { + return nodestream.once(name, FS.Utility.safeCallback(callback)); + }; + + // Return the modified stream - modified anyway + return nodestream; +}; + +/** + * @method FS.Utility.eachFileFromPath + * @public + * @param {String} p - Server path + * @param {Function} f - Function to run for each file found in the path. + * @returns {undefined} + * + * Utility for iteration over files from path on server + */ +FS.Utility.eachFileFromPath = function(p, f) { + var fs = Npm.require('fs'); + var path = Npm.require('path'); + var files = fs.readdirSync(p); + files.map(function (file) { + return path.join(p, file); + }).filter(function (filePath) { + return fs.statSync(filePath).isFile() && path.basename(filePath)[0] !== '.'; + }).forEach(function (filePath) { + f(filePath); + }); +}; diff --git a/packages/wekan-cfs-base-package/internal.api.md b/packages/wekan-cfs-base-package/internal.api.md new file mode 100644 index 000000000..559c4e7a9 --- /dev/null +++ b/packages/wekan-cfs-base-package/internal.api.md @@ -0,0 +1,293 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["base-common.js"](base-common.js) Where: {server|client}__ + +*** + +############################################################################# + +HELPERS + +############################################################################# +- + +### *_utility*.defaultZero(val)  Anywhere ### + +*This method is private* +*This method __defaultZero__ is defined in `_Utility`* + +__Arguments__ + +* __val__ *{Any}* + + Returns number or 0 if value is a falsy + + +> ```_Utility.defaultZero = function(val) { ...``` [base-common.js:42](base-common.js#L42) + + +- + +### *fsUtility*.cloneFileRecord(rec, [options])  Anywhere ### + +*This method __cloneFileRecord__ is defined in `FS.Utility`* + +__Arguments__ + +* __rec__ *{[FS.File](#FS.File)|[FS.Collection filerecord](#FS.Collection filerecord)}* +* __options__ *{Object}* (Optional) + * __full__ *{Boolean}* (Optional, Default = false) + + Set `true` to prevent certain properties from being omitted from the clone. + + +__Returns__ *{Object}* +Cloned filerecord + + +Makes a shallow clone of `rec`, filtering out some properties that might be present if +it's an FS.File instance, but which we never want to be part of the stored +filerecord. + +This is a blacklist clone rather than a whitelist because we want the user to be able +to specify whatever additional properties they wish. + +In general, we expect the following whitelist properties used by the internal and +external APIs: + +_id, name, size, type, chunkCount, chunkSize, chunkSum, copies, createdAt, updatedAt, uploadedAt + +Those properties, and any additional properties added by the user, should be present +in the returned object, which is suitable for inserting into the backing collection or +extending an FS.File instance. + + +> ```FS.Utility.cloneFileRecord = function(rec, options) { ...``` [base-common.js:71](base-common.js#L71) + + +- + +### *fsUtility*.defaultCallback([err])  Anywhere ### + +*This method __defaultCallback__ is defined in `FS.Utility`* + +__Arguments__ + +* __err__ *{[Error](#Error)}* (Optional) + +__Returns__ *{undefined}* + + +Can be used as a default callback for client methods that need a callback. +Simply throws the provided error if there is one. + +> ```FS.Utility.defaultCallback = function defaultCallback(err) { ...``` [base-common.js:96](base-common.js#L96) + + +- + +### *fsUtility*.defaultCallback([f], [err])  Anywhere ### + +*This method __defaultCallback__ is defined in `FS.Utility`* + +__Arguments__ + +* __f__ *{Function}* (Optional) + + A callback function, if you have one. Can be undefined or null. + +* __err__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String](# String)}* (Optional) + + Error or error message (string) + + +__Returns__ *{Any}* +the callback result if any + + +Handle Error, creates an Error instance with the given text. If callback is +a function, passes the error to that function. Otherwise throws it. Useful +for dealing with errors in methods that optionally accept a callback. + +> ```FS.Utility.handleError = function(f, err, result) { ...``` [base-common.js:120](base-common.js#L120) + + +- + +### *fsUtility*.noop()  Anywhere ### + +*This method __noop__ is defined in `FS.Utility`* + +Use this to hand a no operation / empty function + +> ```FS.Utility.noop = function() { ...``` [base-common.js:134](base-common.js#L134) + + +- + +### validateAction(validators, fileObj, userId)  Anywhere ### + +*This method is private* + +__Arguments__ + +* __validators__ *{Object}* + + The validators object to use, with `deny` and `allow` properties. + +* __fileObj__ *{[FS.File](#FS.File)}* + + Mounted or mountable file object to be passed to validators. + +* __userId__ *{String}* + + The ID of the user who is attempting the action. + + +__Returns__ *{undefined}* + + +Throws a "400-Bad Request" Meteor error if the file is not mounted or +a "400-Access denied" Meteor error if the action is not allowed. + +> ```FS.Utility.validateAction = function validateAction(validators, fileObj, userId) { ...``` [base-common.js:147](base-common.js#L147) + + +- + +### *fsUtility*.getFileName(name)  Anywhere ### + +*This method is private* +*This method __getFileName__ is defined in `FS.Utility`* + +__Arguments__ + +* __name__ *{String}* + + A filename, filepath, or URL + + +__Returns__ *{String}* +The filename without the URL, filepath, or query string + + +> ```FS.Utility.getFileName = function utilGetFileName(name) { ...``` [base-common.js:187](base-common.js#L187) + + +- + +### *fsUtility*.getFileExtension(name)  Anywhere ### + +*This method __getFileExtension__ is defined in `FS.Utility`* + +__Arguments__ + +* __name__ *{String}* + + A filename, filepath, or URL that may or may not have an extension. + + +__Returns__ *{String}* +The extension or an empty string if no extension found. + + +> ```FS.Utility.getFileExtension = function utilGetFileExtension(name) { ...``` [base-common.js:205](base-common.js#L205) + + +- + +### *fsUtility*.setFileExtension(name, ext)  Anywhere ### + +*This method __setFileExtension__ is defined in `FS.Utility`* + +__Arguments__ + +* __name__ *{String}* + + A filename that may or may not already have an extension. + +* __ext__ *{String}* + + An extension without leading period, which you want to be the new extension on `name`. + + +__Returns__ *{String}* +The filename with changed extension. + + +> ```FS.Utility.setFileExtension = function utilSetFileExtension(name, ext) { ...``` [base-common.js:222](base-common.js#L222) + + +*** + +__File: ["base-server.js"](base-server.js) Where: {server}__ + +*** + +### *fsUtility*.binaryToBuffer(data)  Server ### + +*This method __binaryToBuffer__ is defined in `FS.Utility`* + +__Arguments__ + +* __data__ *{Uint8Array}* + +__Returns__ *{Buffer}* + + +Converts a Uint8Array instance to a Node Buffer instance + +> ```FS.Utility.binaryToBuffer = function(data) { ...``` [base-server.js:9](base-server.js#L9) + + +- + +### *fsUtility*.bufferToBinary(data)  Server ### + +*This method __bufferToBinary__ is defined in `FS.Utility`* + +__Arguments__ + +* __data__ *{Buffer}* + +__Returns__ *{Uint8Array}* + + +Converts a Node Buffer instance to a Uint8Array instance + +> ```FS.Utility.bufferToBinary = function(data) { ...``` [base-server.js:26](base-server.js#L26) + + +*** + +__File: ["base-client.js"](base-client.js) Where: {client}__ + +*** + +### *fsUtility*.eachFile(e, f)  Client ### + +*This method __eachFile__ is defined in `FS.Utility`* + +__Arguments__ + +* __e__ *{[Event](#Event)}* + + Browser event + +* __f__ *{Function}* + + Function to run for each file found in the event. + + +__Returns__ *{undefined}* + + +Utility for iteration over files in event + +> ```FS.Utility.eachFile = function(e, f) { ...``` [base-client.js:37](base-client.js#L37) + + diff --git a/packages/wekan-cfs-base-package/package.js b/packages/wekan-cfs-base-package/package.js new file mode 100644 index 000000000..82b3f712c --- /dev/null +++ b/packages/wekan-cfs-base-package/package.js @@ -0,0 +1,37 @@ +Package.describe({ + version: '0.0.30', + name: 'wekan-cfs-base-package', + summary: 'CollectionFS, Base package', + git: 'https://github.com/zcfs/Meteor-cfs-base-package.git' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use(['deps', 'underscore', 'ejson']); + + if (api.export) { + api.export('FS'); + api.export('_Utility', { testOnly: true }); + } + + api.addFiles([ + 'base-common.js', + 'base-server.js' + ], 'server'); + + api.addFiles([ + 'polyfill.base64.js', + 'base-common.js', + 'base-client.js' + ], 'client'); +}); + +// Package.on_test(function (api) { +// api.use(['wekan-cfs-base-package', 'cfs-file']); +// api.use('test-helpers', 'server'); +// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', +// 'random', 'deps']); + +// api.add_files('tests/common-tests.js', ['client', 'server']); +// }); diff --git a/packages/wekan-cfs-base-package/polyfill.base64.js b/packages/wekan-cfs-base-package/polyfill.base64.js new file mode 100644 index 000000000..14bf52f75 --- /dev/null +++ b/packages/wekan-cfs-base-package/polyfill.base64.js @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010 Nick Galbreath + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* base64 encode/decode compatible with window.btoa/atob + * + * window.atob/btoa is a Firefox extension to convert binary data (the "b") + * to base64 (ascii, the "a"). + * + * It is also found in Safari and Chrome. It is not available in IE. + * + * if (!window.btoa) window.btoa = base64.encode + * if (!window.atob) window.atob = base64.decode + * + * The original spec's for atob/btoa are a bit lacking + * https://developer.mozilla.org/en/DOM/window.atob + * https://developer.mozilla.org/en/DOM/window.btoa + * + * window.btoa and base64.encode takes a string where charCodeAt is [0,255] + * If any character is not [0,255], then an DOMException(5) is thrown. + * + * window.atob and base64.decode take a base64-encoded string + * If the input length is not a multiple of 4, or contains invalid characters + * then an DOMException(5) is thrown. + */ +var base64 = {}; +base64.PADCHAR = '='; +base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +base64.makeDOMException = function() { + // sadly in FF,Safari,Chrome you can't make a DOMException + var e, tmp; + + try { + return new DOMException(DOMException.INVALID_CHARACTER_ERR); + } catch (tmp) { + // not available, just passback a duck-typed equiv + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype + var ex = new Error("DOM Exception 5"); + + // ex.number and ex.description is IE-specific. + ex.code = ex.number = 5; + ex.name = ex.description = "INVALID_CHARACTER_ERR"; + + // Safari/Chrome output format + ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; }; + return ex; + } +} + +base64.getbyte64 = function(s,i) { + // This is oddly fast, except on Chrome/V8. + // Minimal or no improvement in performance by using a + // object with properties mapping chars to value (eg. 'A': 0) + var idx = base64.ALPHA.indexOf(s.charAt(i)); + if (idx === -1) { + throw base64.makeDOMException(); + } + return idx; +} + +base64.decode = function(s) { + // convert to string + s = '' + s; + var getbyte64 = base64.getbyte64; + var pads, i, b10; + var imax = s.length + if (imax === 0) { + return s; + } + + if (imax % 4 !== 0) { + throw base64.makeDOMException(); + } + + pads = 0 + if (s.charAt(imax - 1) === base64.PADCHAR) { + pads = 1; + if (s.charAt(imax - 2) === base64.PADCHAR) { + pads = 2; + } + // either way, we want to ignore this last block + imax -= 4; + } + + var x = []; + for (i = 0; i < imax; i += 4) { + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | + (getbyte64(s,i+2) << 6) | getbyte64(s,i+3); + x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff)); + } + + switch (pads) { + case 1: + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6); + x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff)); + break; + case 2: + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12); + x.push(String.fromCharCode(b10 >> 16)); + break; + } + return x.join(''); +} + +base64.getbyte = function(s,i) { + var x = s.charCodeAt(i); + if (x > 255) { + throw base64.makeDOMException(); + } + return x; +} + +base64.encode = function(s) { + if (arguments.length !== 1) { + throw new SyntaxError("Not enough arguments"); + } + var padchar = base64.PADCHAR; + var alpha = base64.ALPHA; + var getbyte = base64.getbyte; + + var i, b10; + var x = []; + + // convert to string + s = '' + s; + + var imax = s.length - s.length % 3; + + if (s.length === 0) { + return s; + } + for (i = 0; i < imax; i += 3) { + b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2); + x.push(alpha.charAt(b10 >> 18)); + x.push(alpha.charAt((b10 >> 12) & 0x3F)); + x.push(alpha.charAt((b10 >> 6) & 0x3f)); + x.push(alpha.charAt(b10 & 0x3f)); + } + switch (s.length - imax) { + case 1: + b10 = getbyte(s,i) << 16; + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + + padchar + padchar); + break; + case 2: + b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8); + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + + alpha.charAt((b10 >> 6) & 0x3f) + padchar); + break; + } + return x.join(''); +} + +if (!window.btoa) window.btoa = base64.encode +if (!window.atob) window.atob = base64.decode \ No newline at end of file diff --git a/packages/wekan-cfs-base-package/tests/common-tests.js b/packages/wekan-cfs-base-package/tests/common-tests.js new file mode 100644 index 000000000..310d3b66b --- /dev/null +++ b/packages/wekan-cfs-base-package/tests/common-tests.js @@ -0,0 +1,161 @@ +function equals(a, b) { + return EJSON.stringify(a) === EJSON.stringify(b); +} + +Tinytest.add('cfs-base-package - test environment', function(test) { + test.isTrue(typeof FS !== 'undefined', + 'FS scope not declared'); + + test.isTrue(typeof FS.Store !== 'undefined', + 'FS scope "FS.Store" not declared'); + + test.isTrue(typeof FS.AccessPoint !== 'undefined', + 'FS scope "FS.AccessPoint" not declared'); + + test.isTrue(typeof FS.Utility !== 'undefined', + 'FS scope "FS.Utility" not declared'); + + test.isTrue(typeof FS._collections !== 'undefined', + 'FS scope "FS._collections" not declared'); + + test.isTrue(typeof _Utility !== 'undefined', + '_Utility test scope not declared'); +}); + +Tinytest.add('cfs-base-package - _Utility.defaultZero', function(test) { + test.equal(_Utility.defaultZero(), 0, 'Failes to return 0 when (undefined)'); + test.equal(_Utility.defaultZero(undefined), 0, 'Failes to return 0 when undefined'); + test.equal(_Utility.defaultZero(null), 0, 'Failes to return 0 when null'); + test.equal(_Utility.defaultZero(false), 0, 'Failes to return 0 when false'); + test.equal(_Utility.defaultZero(0), 0, 'Failes to return 0 when 0'); + test.equal(_Utility.defaultZero(-1), -1, 'Failes to return -1'); + test.equal(_Utility.defaultZero(1), 1, 'Failes to return 1'); + test.equal(_Utility.defaultZero(-0.1), -0.1, 'Failes to return -0.1'); + test.equal(_Utility.defaultZero(0.1), 0.1, 'Failes to return 0.1'); + test.equal(_Utility.defaultZero(''), 0, 'Failes to return ""'); + test.equal(_Utility.defaultZero({}), NaN, 'Failes to return NaN when object'); + test.equal(_Utility.defaultZero("dfdsfs"), NaN, 'Failes to return NaN when string'); + test.equal(_Utility.defaultZero("1"), 1, 'Failes to return 1 when string "1"'); +}); + +Tinytest.add('cfs-base-package - FS.Utility.cloneFileRecord', function(test) { + // Given an object with any props, should filter out 'collectionName', + // 'collection', 'data', and 'createdByTransform' + var result = FS.Utility.cloneFileRecord({a: 1, b: {c: 1}, d: [1, 2], collectionName: 'test', collection: {}, data: {}, createdByTransform: false}); + test.equal(result, {a: 1, b: {c: 1}, d: [1, 2]}); + + // Given an FS.File instance, should filter out 'collectionName', + // 'collection', 'data', and 'createdByTransform' and return a plain Object + var fileObj = new FS.File({a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100, collectionName: 'test', collection: {}, data: {}, createdByTransform: false}); + test.isTrue(fileObj instanceof FS.File); + var result = FS.Utility.cloneFileRecord(fileObj); + test.isFalse(result instanceof FS.File); + test.isTrue(equals(result, {a: 1, b: {c: 1}, d: [1, 2], name: 'name.png', type: 'image/png', size: 100})); +}); + +Tinytest.add('cfs-base-package - FS.Utility.defaultCallback', function(test) { + // should throw an error passed in, but not a Meteor.Error + test.throws(function () { + var cb = FS.Utility.defaultCallback; + cb(new Error('test')); + }); + + var cb2 = FS.Utility.defaultCallback; + test.isUndefined(cb2(new Meteor.Error('test'))); +}); + +Tinytest.add('cfs-base-package - FS.Utility.handleError', function(test) { + test.isTrue(true); + // TODO +}); + +Tinytest.add('cfs-base-package - FS.Utility.binaryToBuffer', function(test) { + test.isTrue(true); + // TODO +}); + +Tinytest.add('cfs-base-package - FS.Utility.bufferToBinary', function(test) { + test.isTrue(true); + // TODO +}); + +Tinytest.add('cfs-base-package - FS.Utility.connectionLogin', function(test) { + test.isTrue(true); + // TODO +}); + +Tinytest.add('cfs-base-package - FS.Utility.getFileName', function(test) { + + function t(input, expected) { + var ext = FS.Utility.getFileName(input); + test.equal(ext, expected, 'Got incorrect filename'); + } + + t('bar.png', 'bar.png'); + t('foo/bar.png', 'bar.png'); + t('/foo/foo/bar.png', 'bar.png'); + t('http://foobar.com/file.png', 'file.png'); + t('http://foobar.com/file', 'file'); + t('http://foobar.com/file.png?a=b', 'file.png'); + t('http://foobar.com/.file?a=b', '.file'); + t('file', 'file'); + t('.file', '.file'); + t('foo/.file', '.file'); + t('/foo/foo/.file', '.file'); +}); + +Tinytest.add('cfs-base-package - FS.Utility.getFileExtension', function(test) { + + function t(input, expected) { + var ext = FS.Utility.getFileExtension(input); + test.equal(ext, expected, 'Got incorrect extension'); + } + + t('bar.png', 'png'); + t('foo/bar.png', 'png'); + t('/foo/foo/bar.png', 'png'); + t('http://foobar.com/file.png', 'png'); + t('http://foobar.com/file', ''); + t('http://foobar.com/file.png?a=b', 'png'); + t('http://foobar.com/file?a=b', ''); + t('file', ''); + t('.file', ''); + t('foo/.file', ''); + t('/foo/foo/.file', ''); +}); + +Tinytest.add('cfs-base-package - FS.Utility.setFileExtension', function(test) { + + function t(name, ext, expected) { + var newName = FS.Utility.setFileExtension(name, ext); + test.equal(newName, expected, 'Extension was not set correctly'); + } + + t('bar.png', 'jpeg', 'bar.jpeg'); + t('bar', 'jpeg', 'bar.jpeg'); + t('.bar', 'jpeg', '.bar.jpeg'); + t('', 'jpeg', ''); + t(null, 'jpeg', null); +}); + +//Test API: +//Tinytest.add('', function(test) {}); +//Tinytest.addAsync('', function(test, onComplete) {}); +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-collection-filters/.travis.yml b/packages/wekan-cfs-collection-filters/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-collection-filters/LICENSE.md b/packages/wekan-cfs-collection-filters/LICENSE.md new file mode 100644 index 000000000..b8d7fab60 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-collection-filters/README.md b/packages/wekan-cfs-collection-filters/README.md new file mode 100644 index 000000000..bf86e25e3 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/README.md @@ -0,0 +1,8 @@ +wekan-cfs-collection-filters +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. \ No newline at end of file diff --git a/packages/wekan-cfs-collection-filters/api.md b/packages/wekan-cfs-collection-filters/api.md new file mode 100644 index 000000000..2013ed0a5 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/api.md @@ -0,0 +1,44 @@ +## cfs-collection-filters Public API ## + +CollectionFS, adds FS.Collection filters + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +- + +### *fsCollection*.filters(filters)  Anywhere ### + +*This method __filters__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __filters__ *{Object}* + + File filters for this collection. + + +__Returns__ *{undefined}* + + +> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7) + + +- + +### *fsCollection*.allowsFile()  Anywhere ### + +*This method __allowsFile__ is defined in `prototype` of `FS.Collection`* + +__Returns__ *{boolean}* +True if the collection allows this file. + + +Checks based on any filters defined on the collection. If the +file is not valid according to the filters, this method returns false +and also calls the filter `onInvalid` method defined for the +collection, passing it an English error string that explains why it +failed. + +> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108) + + diff --git a/packages/wekan-cfs-collection-filters/filters.js b/packages/wekan-cfs-collection-filters/filters.js new file mode 100644 index 000000000..52bae78a6 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/filters.js @@ -0,0 +1,191 @@ +/** + * @method FS.Collection.prototype.filters + * @public + * @param {Object} filters - File filters for this collection. + * @returns {undefined} + */ +FS.Collection.prototype.filters = function fsColFilters(filters) { + var self = this; + + // Check filter option values and normalize them for quicker checking later + if (filters) { + // check/adjust allow/deny + FS.Utility.each(['allow', 'deny'], function (type) { + if (!filters[type]) { + filters[type] = {}; + } else if (typeof filters[type] !== "object") { + throw new Error(type + ' filter must be an object'); + } + }); + + // check/adjust maxSize + if (typeof filters.maxSize === "undefined") { + filters.maxSize = null; + } else if (filters.maxSize && typeof filters.maxSize !== "number") { + throw new Error('maxSize filter must be an number'); + } + + // check/adjust extensions + FS.Utility.each(['allow', 'deny'], function (type) { + if (!filters[type].extensions) { + filters[type].extensions = []; + } else if (!FS.Utility.isArray(filters[type].extensions)) { + throw new Error(type + '.extensions filter must be an array of extensions'); + } else { + //convert all to lowercase + for (var i = 0, ln = filters[type].extensions.length; i < ln; i++) { + filters[type].extensions[i] = filters[type].extensions[i].toLowerCase(); + } + } + }); + + // check/adjust content types + FS.Utility.each(['allow', 'deny'], function (type) { + if (!filters[type].contentTypes) { + filters[type].contentTypes = []; + } else if (!FS.Utility.isArray(filters[type].contentTypes)) { + throw new Error(type + '.contentTypes filter must be an array of content types'); + } + }); + + self.options.filter = filters; + } + + // Define deny functions to enforce file filters on the server + // for inserts and updates that initiate from untrusted code. + self.files.deny({ + insert: function(userId, fsFile) { + return !self.allowsFile(fsFile); + }, + update: function(userId, fsFile, fields, modifier) { + // TODO will need some kind of additional security here: + // Don't allow them to change the type, size, name, and + // anything else that would be security or data integrity issue. + // Such security should probably be added by cfs-collection package, not here. + return !self.allowsFile(fsFile); + }, + fetch: [] + }); + + // If insecure package is in use, we need to add allow rules that return + // true. Otherwise, it would seemingly turn off insecure mode. + if (Package && Package.insecure) { + self.allow({ + insert: function() { + return true; + }, + update: function() { + return true; + }, + remove: function() { + return true; + }, + download: function() { + return true; + }, + fetch: [], + transform: null + }); + } + // If insecure package is NOT in use, then adding the deny function + // does not have any effect on the main app's security paradigm. The + // user will still be required to add at least one allow function of her + // own for each operation for this collection. And the user may still add + // additional deny functions, but does not have to. +}; + +/** + * @method FS.Collection.prototype.allowsFile Does the collection allow the specified file? + * @public + * @returns {boolean} True if the collection allows this file. + * + * Checks based on any filters defined on the collection. If the + * file is not valid according to the filters, this method returns false + * and also calls the filter `onInvalid` method defined for the + * collection, passing it an English error string that explains why it + * failed. + */ +FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { + var self = this; + + // Get filters + var filter = self.options.filter; + if (!filter) { + return true; + } + var saveAllFileExtensions = (filter.allow.extensions.length === 0); + var saveAllContentTypes = (filter.allow.contentTypes.length === 0); + + // Get info about the file + var filename = fileObj.name(); + var contentType = fileObj.type(); + if (!saveAllContentTypes && !contentType) { + filter.onInvalid && filter.onInvalid(filename + " has an unknown content type"); + return false; + } + var fileSize = fileObj.size(); + if (!fileSize || isNaN(fileSize)) { + filter.onInvalid && filter.onInvalid(filename + " has an unknown file size"); + return false; + } + + // Do extension checks only if we have a filename + if (filename) { + var ext = fileObj.getExtension(); + if (!((saveAllFileExtensions || + FS.Utility.indexOf(filter.allow.extensions, ext) !== -1) && + FS.Utility.indexOf(filter.deny.extensions, ext) === -1)) { + filter.onInvalid && filter.onInvalid(filename + ' has the extension "' + ext + '", which is not allowed'); + return false; + } + } + + // Do content type checks + if (!((saveAllContentTypes || + contentTypeInList(filter.allow.contentTypes, contentType)) && + !contentTypeInList(filter.deny.contentTypes, contentType))) { + filter.onInvalid && filter.onInvalid(filename + ' is of the type "' + contentType + '", which is not allowed'); + return false; + } + + // Do max size check + if (typeof filter.maxSize === "number" && fileSize > filter.maxSize) { + filter.onInvalid && filter.onInvalid(filename + " is too big"); + return false; + } + return true; +}; + +/** + * @method contentTypeInList Is the content type string in the list? + * @private + * @param {String[]} list - Array of content types + * @param {String} contentType - The content type + * @returns {Boolean} + * + * Returns true if the content type is in the list, or if it matches + * one of the special types in the list, e.g., "image/*". + */ +function contentTypeInList(list, contentType) { + var listType, found = false; + for (var i = 0, ln = list.length; i < ln; i++) { + listType = list[i]; + if (listType === contentType) { + found = true; + break; + } + if (listType === "image/*" && contentType.indexOf("image/") === 0) { + found = true; + break; + } + if (listType === "audio/*" && contentType.indexOf("audio/") === 0) { + found = true; + break; + } + if (listType === "video/*" && contentType.indexOf("video/") === 0) { + found = true; + break; + } + } + return found; +} diff --git a/packages/wekan-cfs-collection-filters/internal.api.md b/packages/wekan-cfs-collection-filters/internal.api.md new file mode 100644 index 000000000..adecf64ac --- /dev/null +++ b/packages/wekan-cfs-collection-filters/internal.api.md @@ -0,0 +1,72 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["filters.js"](filters.js) Where: {client|server}__ + +*** + +### *fsCollection*.filters(filters)  Anywhere ### + +*This method __filters__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __filters__ *{Object}* + + File filters for this collection. + + +__Returns__ *{undefined}* + + +> ```FS.Collection.prototype.filters = function fsColFilters(filters) { ...``` [filters.js:7](filters.js#L7) + + +- + +### *fsCollection*.allowsFile()  Anywhere ### + +*This method __allowsFile__ is defined in `prototype` of `FS.Collection`* + +__Returns__ *{boolean}* +True if the collection allows this file. + + +Checks based on any filters defined on the collection. If the +file is not valid according to the filters, this method returns false +and also calls the filter `onInvalid` method defined for the +collection, passing it an English error string that explains why it +failed. + +> ```FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { ...``` [filters.js:108](filters.js#L108) + + +- + +### contentTypeInList(list, contentType)  undefined ### + +*This method is private* + +__Arguments__ + +* __list__ *{[String[]](#String[])}* + + Array of content types + +* __contentType__ *{String}* + + The content type + + +__Returns__ *{Boolean}* + + +Returns true if the content type is in the list, or if it matches +one of the special types in the list, e.g., "image/*". + +> ```function contentTypeInList(list, contentType) { ...``` [filters.js:169](filters.js#L169) + + diff --git a/packages/wekan-cfs-collection-filters/package.js b/packages/wekan-cfs-collection-filters/package.js new file mode 100644 index 000000000..5a1c8c385 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/package.js @@ -0,0 +1,29 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-cfs-collection-filters.git', + name: 'wekan-cfs-collection-filters', + version: '0.2.4', + summary: 'CollectionFS, adds FS.Collection filters' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-collection@0.5.4']); + + api.addFiles([ + 'filters.js' + ], 'client'); + + api.addFiles([ + 'filters.js' + ], 'server'); +}); + +// Package.on_test(function (api) { +// api.use('collectionfs'); +// api.use('test-helpers', 'server'); +// api.use(['tinytest']); + +// api.addFiles('tests/server-tests.js', 'server'); +// api.addFiles('tests/client-tests.js', 'client'); +// }); diff --git a/packages/wekan-cfs-collection-filters/tests/client-tests.js b/packages/wekan-cfs-collection-filters/tests/client-tests.js new file mode 100644 index 000000000..0ccc90047 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/tests/client-tests.js @@ -0,0 +1,27 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-collection-filters - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-collection-filters/tests/server-tests.js b/packages/wekan-cfs-collection-filters/tests/server-tests.js new file mode 100644 index 000000000..bfaaf6ef9 --- /dev/null +++ b/packages/wekan-cfs-collection-filters/tests/server-tests.js @@ -0,0 +1,27 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-collection-filters - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-collection/.travis.yml b/packages/wekan-cfs-collection/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-collection/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-collection/CHANGELOG.md b/packages/wekan-cfs-collection/CHANGELOG.md new file mode 100644 index 000000000..cfe39a6a0 --- /dev/null +++ b/packages/wekan-cfs-collection/CHANGELOG.md @@ -0,0 +1,727 @@ +# Changelog + +## vCurrent +## [v0.5.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.3) +#### 20/12/14 by Morten Henriksen +- add changelog + +- Bump to version 0.5.3 + +## [v0.5.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.2) +#### 17/12/14 by Morten Henriksen +- Bump to version 0.5.2 + +## [v0.5.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.1) +#### 17/12/14 by Morten Henriksen +- mbr update, remove versions.json + +- Bump to version 0.5.1 + +## [v0.5.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.0) +#### 17/12/14 by Morten Henriksen +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- mbr update versions and fix warnings + +- update pkg, dependencies, etc. + +- Fix #483 + +- *Merged pull-request:* "mrt graphicsmagick instructions to meteor 0.9+" [#442](https://github.com/zcfs/Meteor-CollectionFS/issues/442) ([yogiben](https://github.com/yogiben)) + +- mrt graphicsmagick instructions to meteor 0.9+ + +- mention underlying collection + +- *Merged pull-request:* "Added server-side Buffer example to README.md" [#405](https://github.com/zcfs/Meteor-CollectionFS/issues/405) ([abuddenb](https://github.com/abuddenb)) + +- change prop names + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- add "Display an Uploaded Image" example + +- addition to steps + +- correct 0.9.0 steps + +- Merge branch 'devel' of github.com:abuddenb/Meteor-CollectionFS into devel + +- Added server-side Buffer example to README.md + +Patches by GitHub users [@yogiben](https://github.com/yogiben), [@abuddenb](https://github.com/abuddenb). + +## [v0.4.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.9) +#### 27/08/14 by Eric Dobbertin +- add 0.9.0 instructions + +- change package name to lowercase + +- Added server-side Buffer example to README.md + +- *Merged pull-request:* "Updated README.md to reflect that FS.File - Objects can't be stored in t..." [#395](https://github.com/zcfs/Meteor-CollectionFS/issues/395) ([DanielDornhardt](https://github.com/DanielDornhardt)) + +- Updated README.md to reflect that FS.File - Objects can't be stored in the Server MongoDB at the moment. + +- document key + +- update the descriptions of collections + +- document beforeWrite + +- add download button example + +- *Merged pull-request:* "Update README.md" [#354](https://github.com/zcfs/Meteor-CollectionFS/issues/354) ([karabijavad](https://github.com/karabijavad)) + +- should say to use cfs-graphicsmagick pkg + +- fix typo + +- *Merged pull-request:* "Added "Storing FS.File references in your objects" chapter to README" [#311](https://github.com/zcfs/Meteor-CollectionFS/issues/311) ([Sanjo](https://github.com/Sanjo)) + +- Added "Storing FS.File references in your objects" chapter to README + +- a few more API updates + +- updates for changed FS.File API + +- additions and clarifications + +- *Merged pull-request:* "Custom Metadata misleading example" [#282](https://github.com/zcfs/Meteor-CollectionFS/issues/282) ([czeslaaw](https://github.com/czeslaaw)) + +- Custom Metadata misleading example + +Patches by GitHub users [@DanielDornhardt](https://github.com/DanielDornhardt), [@karabijavad](https://github.com/karabijavad), [@Sanjo](https://github.com/Sanjo), [@czeslaaw](https://github.com/czeslaaw). + +## [v0.4.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.8) +#### 09/04/14 by Eric Dobbertin +- Add cfs-collection-filters pkg by default + +- Use FS.Utility.setFileExtension in example + +## [v0.4.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.7) +#### 05/04/14 by Morten Henriksen +- corrections, and move image examples here so we can deprecate cfs-imagemagick + +## [v0.4.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.6) +#### 02/04/14 by Morten Henriksen +- *Fixed bug:* "Documentation Issues" [#241](https://github.com/zcfs/Meteor-CollectionFS/issues/241) + +- point to cfs-graphicsmagick readme for transform examples + +## [v0.4.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.5) +#### 31/03/14 by Eric Dobbertin +- use latest releases + +- Add yet a note about mrt and collectionFS in the readme + +## [v0.4.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.4) +#### 25/03/14 by Morten Henriksen +- attempt to get everything up to date + +- Add notice about mrt having troubles figuring out deps + +- *Merged pull-request:* "Removed redundant installation instructions." [#212](https://github.com/zcfs/Meteor-CollectionFS/issues/212) ([lleonard188](https://github.com/lleonard188)) + +- up to date + +- read me update + +- add test note + +Patches by GitHub user [@lleonard188](https://github.com/lleonard188). + +## [v0.4.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.3) +#### 23/03/14 by Morten Henriksen +- use collectionFS travis version force update + +## [v0.4.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.2) +#### 22/03/14 by Morten Henriksen +- change deps + +- Add read me about transformWrite + +## [v0.4.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.1) +#### 21/03/14 by Morten Henriksen +- update reference to devel branch + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- don't need to install transfer + +- mark deprecated api docs + +- test strikeout md + +- no need to imply cfs-transfer since it doesn't do anything at the moment + +- Remove ejson-file imply + +## [v0.4.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.0) +#### 06/03/14 by Eric Dobbertin +- add section for documenting example code for common tasks, and add some of the examples; @raix feel free to add more + +- add http upload package to smart.json + +- Merge origin/devel into devel + +- need to imply the http upload package because it's the default now + +- update HTTP access point override docs + +- update docs to use `devel` branch + +- list component packages in smart.json + +- Merge origin/devel into devel-merge + +- add the ejson-file and correct doc + +- refactor everything into packages + +## [devel-merge-old] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-merge-old) +#### 12/02/14 by Eric Dobbertin +- update to latest FileSaver.js + +- one of many refactores + +- add optimization section + +- add section explaining client vs server insert + +- prevent autopublish for the SA collections + +- remove arg that isn't used or passed + +- changes to support client SA, plus clean/reorg SA code to have less duplication + +- merge the concepts of "store" and "copy", update docs, switch to FS.Store namespace + +- use wait:true to avoid Meteor issue + +- extend allow when insecure package is installed + +- document and improve code flow a bit + +- faster to use onResultReceived callback + +- use onload instead of onloadend because we want only successful loads + +- remove some console logging + +- fix issue where stuff wasn't uploading because we weren't calling getFileRecord() in the access point methods + +- make allowed file extension checks not be case sensitive + +- remove DDP "/del" access point since it's not used or necessary + +- Remove collection-hooks dependency; use deny instead. Also some cleanup and code docs + +- replace `mmmagic` dependency with a `mime` dependency; hopefully fixes issues we've seen with meteor deploy + +- refactor to expose saveCopy to API; then change fileworker observes to be per-copy, calling saveCopy and allowing better control of which copies to create at which times + +- use observes for all store saving and temp store deleting; add/adjust some api doc comments (didn't regenerate yet) + +- improve fileIsAllowed check order and messages + +- Add check for the Accounts package + +- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120) + +- always append access token to url when a user is logged in; makes usage simpler to do opt-out rather than opt-in + +- Don't queue ddp method calls + +- Merge changes + +- Optimize buffer handling + +- Switch to MicroQueue + +- improve memory management, attempt client side resume implementation, other minor fixes + +- fix several issues, make downloading work, improve a few bits of code + +- do the isImage test correctly; add some API docs + +- isEmpty will be true for null or undefined + +- rewrite getExtension so it works for unmounted files, too + +- add strong reactive-list dependency for powerqueue + +- minor fixes and code improvements; track uploading files by both collection and ID since one queue handles all collections + +- Base the upload queue on PowerQueue sub queues + +- Add sa note + +- limit use of db + +- Refactor and documentation + +- use mmmagic 0.3.5 + +- *Fixed bug:* "Option to set Cache-Control and Max-Age" [#117](https://github.com/zcfs/Meteor-CollectionFS/issues/117) + +- fix stuff that's broken + +## [v0.3.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.7) +#### 08/01/14 by Morten Henriksen +- *Fixed bug:* "How to store files on the server side?" [#29](https://github.com/zcfs/Meteor-CollectionFS/issues/29) + +- rework ejson and remove fsFile.reload + +- explain complete upload process in ADVANCED docs + +- Adds join to smart.json (its a weak dependency #119) + +- link to api docs for acceptDropsOn + +- typo/formatting fixes + +- docs + insert callback returns `FS.File` instead of `id` + +- Add docs and `FS.File.fetch` + +- Add support for `Join` + +- end with line + +- smaller headlines in docs + +- init api docs + +- Refactor and documentation + +- FS.Collection.insert should return the `FS.File` object + +- remove some comments and such + +- document custom connections + +- add livedata ref to access DDP obj + +- use separate ddp connection with option to pass in custom + +- public folder and gm/im + +- fix null options + +- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120) + +- make useHTTP true by default + +- skip auth checks if Package.insecure + +- remove `callback` arg from fsFile.get since it's not used or necessary; fix partial gets such that they actually use getBytes, greatly speeding up downloads of large files + +- reorg code and speed up downloads + +- split TransferQueue into DownloadTransferQueue and UploadTransferQueue + +- improvements to make use of new PowerQueue features + +- fix issue with previous commit + +- add accessPoints option + +- Pull out temporary chunk code into a separate tempStore.js file, within a TempStore object. This makes it easier to maintain. Also updated the file worker code to correctly find temporary chunks that can be removed and delete those files. + +- add security section + +- Internally, change all "master" stuff to be the same as "copies". External API is still the same, but master options are copied to a special copy named "_master" so that all the other code can be cleaner. This may be a step toward being able to blur or eliminate the master/copy distinction, although there are still some benefits to having a master. + +- Add instructions for installing for testing + +- refactor code to be a bit cleaner + +- add functions for getting an FS.File or setting FS.File data from a URL on the server + +- clean up and improve some transfer code + +- update console log message to be more correct + +- ensure that fsFile.bytesUploaded is always set correctly + +- fix some issues with recent commits + +- *Merged pull-request:* "Updates the filter example area so that it works" [#109](https://github.com/zcfs/Meteor-CollectionFS/issues/109) ([cramhead](https://github.com/cramhead)) + +- add .npm to gitignore + +- Remove .npm folder + +- Clean clone just a bit + +- Refactor fsCollection and argParser + +- Comment on filter options + +- Add download url + +- refactor access point + +- add put/get/del security based on allow/deny functions + +- Updates the filter example area so that it works + +- update for API change + +- more client-side speed improvements and allow passing File/Blob to FS.File constructor again + +- fix data mixup + +- fix upload slowness and blocking + +- change API to adjust issue with data loading callbacks + +- revise FS.File API where data handling is concerned; fix some issues with callback handling in client-side methods; upshot should be faster, smoother uploads and downloads + +- change names and put everything in exported FS namespace + +- fix get/download of copies + +- *Fixed bug:* "Files after certain size aren't saved properly. " [#104](https://github.com/zcfs/Meteor-CollectionFS/issues/104) + +- add fileobject metadata and acceptDropsOn + +- add some methods to load FO data from URL + +- Correct allow/deny examples + +- call put callback correctly + +- add correct temporary installation instructions + +- use correct filename when saving download + +- filtering fixes + +- removed some unused stuff + +- adjust some comments + +- switch api.remove to api.del for consistency with the other methods + +- add hasCopy method + +- new api; tons of changes + +- use generic queue for server file handling + +- update progress in the correct place + +- minor changes to comments + +- fix gridfs get method + +- incorporate #82 + +- make filtering work (added collection-hooks dependency for core package) + +- change UploadsCollection to CollectionFS; change former CollectionFS to GridFS and don't export it (used only by the gridfs storage adaptor); clean up some other areas and update readmes + +- *Fixed bug:* "retrieveBlob failure" [#93](https://github.com/zcfs/Meteor-CollectionFS/issues/93) + +- implement http methods URLs + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS.git into devel + +- significant revisions to move downloading support to the UploadsCollection and make collectionFS/gridFS a pure storage adaptor + +- Merge branch 'pr/94' into devel + +- split and revise readmes + +- rename packages and organize package.js + +- error handling improvements + +- better failure handling for removeCopy + +- handlebars helper to display blob image in CFS package + +- remove all encoding info + +- refactor to fix multiple-file simultaneous uploads + +- don't use _id in filesystem destination since it's not set anyway + +- remove FileObject.file and instead save file as Blob (.blob) when FileObject.fromFile is called + +- revise API a bit + +- stop using strings and encoding and pass everything as Uint8Array (fixes downloading corruption) + +- only attempt to delete file if it exists + +- remove unused file + +- complete refactoring; temporary for testing/tweaking and then will split into multiple packages + +- *Merged pull-request:* "Implemented max parallel transfers" [#62](https://github.com/zcfs/Meteor-CollectionFS/issues/62) ([floo51](https://github.com/floo51)) + +Patches by GitHub users [@cramhead](https://github.com/cramhead), [@floo51](https://github.com/floo51). + +## [v0.3.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.6) +#### 10/10/13 by Morten Henriksen +- Edit ideas about storage adapters and filehandlers + +- Add MIT License + +- Add paypal and weak deps + +- Added the org. filemanager demo/example + +## [v0.3.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.5) +#### 20/09/13 by Morten Henriksen +## [v0.3.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.4) +#### 20/09/13 by Morten Henriksen +- Extract the examples from collectionFS + +- Added examples from @mxab + +## [v0.3.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.3) +#### 18/09/13 by Morten Henriksen +- added travis badge + +- Added travis badge + +- Added basic environment + +- Added metadata getter to docs + +## [v0.3.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.2) +#### 17/09/13 by Morten Henriksen +## [v0.3.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.1) +#### 16/09/13 by Morten Henriksen +- Added some details about http.publishing of collections + +- added check in filehandlers + +- Updated som docs + +- js hint added scope + +- jshint clean up + +- *Fixed bug:* "Exception from setTimeout callback: { stack: [Getter] }" [#64](https://github.com/zcfs/Meteor-CollectionFS/issues/64) + +- Merge branch 'devel' + +- Bump version to preview of 0.3.0 + +- Implemented max parallel transfers + +- Revert "began refractoring" + +- Revert "Run jshint through files" + +- Revert "Preparing file object methods for better api" + +- Revert "added fileobject files to package" + +- added fileobject files to package + +- Preparing file object methods for better api + +- Run jshint through files + +- *Merged pull-request:* "Improvements and Fixes to built-in helpers, storeFiles, and acceptDropsOn" [#51](https://github.com/zcfs/Meteor-CollectionFS/issues/51) ([aldeed](https://github.com/aldeed)) + +- Add generic events system, switch to "enums" for invalid event types, and change "fileFilter" to "filter" throughout. Update README to reflect these changes. + +- Update readme to reflect storeFiles and acceptDropsOn changes, plus add documentation for all of the new built-in handlebars helpers + +- commit some files that should be committed + +- -Improve and fix built-in handlebars helpers -Prefix all built-in helpers with "cfs" -Update new example app to reflect helper changes, and improve and fix it a bit, too + +- Merge remote-tracking branch 'upstream/master' + +- update documentation of storeFiles and acceptDropsOn + +- Added credit to @eprochasson + +- *Merged pull-request:* "Should fix issue #45" [#50](https://github.com/zcfs/Meteor-CollectionFS/issues/50) ([eprochasson](https://github.com/eprochasson)) + +- *Fixed bug:* "file handlers not showing up in local demo" [#45](https://github.com/zcfs/Meteor-CollectionFS/issues/45) + +- *Merged pull-request:* "Update README.md" [#49](https://github.com/zcfs/Meteor-CollectionFS/issues/49) ([eprochasson](https://github.com/eprochasson)) + +- Added meteor style guide for jshint + +- fix package file + +- fixes and improvements to storeFiles() and acceptDropsOn() + +- Use a different saveAs shim + +- Merge branch 'master' of https://github.com/aldeed/Meteor-CollectionFS + +- Fixes and changes to support cfs changes + +- -"cfs" prefix, new helpers, improvements and fixes -include saveAs shim in the package so that download button helper can reliably call it + +- Add dependencies and files + +- Document storeFiles() and acceptDropsOn() + +- *Merged pull-request:* "Built-ins, fixes, etc." [#48](https://github.com/zcfs/Meteor-CollectionFS/issues/48) ([aldeed](https://github.com/aldeed)) + +- Add the new files to the package manifest + +- Add underscore as dependency. It seems that Meteor may soon remove underscore from the core. + +- Minor changes to support fileHanders() and fileFilter() function changes + +- Copy in numeral.js for use by the built-in handlebar helper that displays file size in human readable format. This means the helper supports any of the format strings supported by numeral.js for file sizes. + +- -Add fileFilter() function to specify allowed and disallowed files per collectionFS, based on extensions and/or content types -Add fileIsAllowed() function to easily check whether a particular file is allowed based on the rules set up by fileFilter() -Change all of the passthrough functions (find, findOne, update, remove, allow, deny) to pass through all function arguments more simply and more safely. This allows, for example, using find() instead of find({}). -Change fileHandlers() to extend the object whenever called, which means you can safely call it more than once -Add several utility functions for use in either client or server code + +- -Add storeFiles API -Check that files are allowed by fileFilter before saving -Add acceptDropsOn API -Use .depend() instead of Deps throughout -Pull out _getProgress calc to use in two places -Add isUploading API -Improve isDownloading code + +- New file to hold built-in handlebars helpers, including several initial helpers + +- New file manager example app showing how to use new features, built-in handlebars helpers, etc. + +- *Merged pull-request:* "Minor doc correction" [#47](https://github.com/zcfs/Meteor-CollectionFS/issues/47) ([aldeed](https://github.com/aldeed)) + +- *Merged pull-request:* "Documentation Improvement" [#46](https://github.com/zcfs/Meteor-CollectionFS/issues/46) ([aldeed](https://github.com/aldeed)) + +- Extensively revised README + +Patches by GitHub users [@aldeed](https://github.com/aldeed), [@eprochasson](https://github.com/eprochasson). + +## [v0.2.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.3) +#### 04/05/13 by Morten Henriksen +- *Fixed bug:* "Binary File Transfers Corrupted? Truncated?" [#41](https://github.com/zcfs/Meteor-CollectionFS/issues/41) + +## [v0.2.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.2) +#### 03/05/13 by Morten Henriksen +- updated scope for serverConsole + +- *Fixed bug:* "Error when using fresh meteor install" [#40](https://github.com/zcfs/Meteor-CollectionFS/issues/40) + +- more text edits + +- Minor text edits + +- Add credit to README + +- *Merged pull-request:* "Option for file encoding" [#36](https://github.com/zcfs/Meteor-CollectionFS/issues/36) ([nhibner](https://github.com/nhibner)) + +- Typo fix. + +- Updated documentation (added encoding to the fileRecord structure). + +- File encoding is stored in the fileRecord. + +- Allow the user to specify an encoding for the buffer when storing on the server. + +- *Merged pull-request:* "Fix backwards incompatibility" [#33](https://github.com/zcfs/Meteor-CollectionFS/issues/33) ([mitar](https://github.com/mitar)) + +- Fix backwards incompatibility. + +Patches by GitHub users [@nhibner](https://github.com/nhibner), [@mitar](https://github.com/mitar). + +## [v0.2.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.1) +#### 08/04/13 by Morten Henriksen +- Minor fixes, added dragndrop, minor refractoring + +- Only work on completed files + +## [v0.2.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.0) +#### 06/04/13 by Morten Henriksen +- Bump to 0.2.0 - Nice + +- Big speed and refractoring + +## [v0.1.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.9) +#### 31/03/13 by Morten Henriksen +- Added some more doc, deprecated autosubscribe to autopublish instead + +## [v0.1.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.8) +#### 31/03/13 by Morten Henriksen +- Added maxFilehandlers to the doc + +## [devel-#27-fixed] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-#27-fixed) +#### 31/03/13 by Morten Henriksen +- Added documentation by @petrocket + +- Got filehandlers up and running in bundles + +- Make a seperate thread + connection foreach collectionFS + +## [v0.1.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.7) +#### 14/03/13 by Morten Henriksen +## [v0.1.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.6) +#### 13/01/13 by Morten Henriksen +- Converted .length to string to cope with Meteor use of underscore + +## [devel-server-cache] (https://github.com/zcfs/Meteor-CollectionFS/tree/devel-server-cache) +#### 11/01/13 by Morten Henriksen +## [v0.1.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.5) +#### 11/01/13 by Morten Henriksen +- On going tests - db updates gone, requires refresh to commit changes to db - guess some que not working + +- removed setTimeout when spawn == 1 + +## [v0.1.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.4) +#### 08/01/13 by Morten Henriksen +## [v0.1.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.3) +#### 08/01/13 by Morten Henriksen +## [v0.1.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.2) +#### 08/01/13 by Morten Henriksen +## [v0.1.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.1) +#### 08/01/13 by Morten Henriksen +- Big one, added fileHandler to create cashed versions of the file + +- added made with Meteor in fileHandler example + +- corrected param bug in find and findOne + +- minor fix and update + +- Corrected install guide to use Meteorite + +## [v0.1.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.0) +#### 07/01/13 by Morten Henriksen +## [v0.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1) +#### 07/01/13 by Morten Henriksen +- updated file strukture and example + +- Restructure to package system + +- Refractoring filenames and edit package.js + +- Package.js added + +- Added smart.json for Atmosphere packages - Not tested! + +- Readme styling corrected + +- Added comments to the upload, storeFile + +- StoreFile should return fileId or null + +- Added notes about security + +- Added only owner can resume, makes sense for now + +- Added how to make a download... + +- Changed project title git + +- And some more corrections + +- More readme text + +- Added some short reference + +- some more md + +- Initial commit + diff --git a/packages/wekan-cfs-collection/LICENSE.md b/packages/wekan-cfs-collection/LICENSE.md new file mode 100644 index 000000000..1a3820821 --- /dev/null +++ b/packages/wekan-cfs-collection/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-collection/README.md b/packages/wekan-cfs-collection/README.md new file mode 100644 index 000000000..8699de9a4 --- /dev/null +++ b/packages/wekan-cfs-collection/README.md @@ -0,0 +1,8 @@ +wekan-cfs-collection +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. \ No newline at end of file diff --git a/packages/wekan-cfs-collection/api.common.js b/packages/wekan-cfs-collection/api.common.js new file mode 100644 index 000000000..42e4f1714 --- /dev/null +++ b/packages/wekan-cfs-collection/api.common.js @@ -0,0 +1,260 @@ +/** @method FS.Collection.prototype.insert Insert `File` or `FS.File` or remote URL into collection + * @public + * @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} fileRef File, FS.File, or other data to insert + * @param {function} [callback] Callback `function(error, fileObj)` + * @returns {FS.File|undefined} The `file object` + * [Meteor docs](http://docs.meteor.com/#insert) + */ +FS.Collection.prototype.insert = function(fileRef, callback) { + var self = this; + + if (Meteor.isClient && !callback) { + callback = FS.Utility.defaultCallback; + } + + // XXX: + // We should out factor beginStorage to FS.File.beginStorage + // the client side storage adapters should be the one providing + // the upload either via http/ddp or direct upload + // Could be cool to have a streaming api on the client side + // having a createReadStream etc. on the client too... + function beginStorage(fileObj) { + + // If on client, begin uploading the data + if (Meteor.isClient) { + self.options.uploader && self.options.uploader(fileObj); + } + + // If on the server, save the binary to a single chunk temp file, + // so that it is available when FileWorker calls saveCopies. + // This will also trigger file handling from collection observes. + else if (Meteor.isServer) { + fileObj.createReadStream().pipe(FS.TempStore.createWriteStream(fileObj)); + } + } + + // XXX: would be great if this function could be simplyfied - if even possible? + function checkAndInsert(fileObj) { + // Check filters. This is called in deny functions, too, but we call here to catch + // server inserts and to catch client inserts early, allowing us to call `onInvalid` on + // the client and save a trip to the server. + if (!self.allowsFile(fileObj)) { + return FS.Utility.handleError(callback, 'FS.Collection insert: file does not pass collection filters'); + } + + // Set collection name + fileObj.collectionName = self.name; + + // Insert the file into db + // We call cloneFileRecord as an easy way of extracting the properties + // that need saving. + if (callback) { + fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj), function(err, id) { + if (err) { + if (fileObj._id) { + delete fileObj._id; + } + } else { + // Set _id, just to be safe, since this could be before or after the insert method returns + fileObj._id = id; + // Pass to uploader or stream data to the temp store + beginStorage(fileObj); + } + callback(err, err ? void 0 : fileObj); + }); + } else { + fileObj._id = self.files.insert(FS.Utility.cloneFileRecord(fileObj)); + // Pass to uploader or stream data to the temp store + beginStorage(fileObj); + } + return fileObj; + } + + // Parse, adjust fileRef + if (fileRef instanceof FS.File) { + return checkAndInsert(fileRef); + } else { + // For convenience, allow File, Blob, Buffer, data URI, filepath, URL, etc. to be passed as first arg, + // and we will attach that to a new fileobj for them + var fileObj = new FS.File(fileRef); + if (callback) { + fileObj.attachData(fileRef, function attachDataCallback(error) { + if (error) { + callback(error); + } else { + checkAndInsert(fileObj); + } + }); + } else { + // We ensure there's a callback on the client, so if there isn't one at this point, + // we must be on the server expecting synchronous behavior. + fileObj.attachData(fileRef); + checkAndInsert(fileObj); + } + return fileObj; + } +}; + +/** @method FS.Collection.prototype.update Update the file record + * @public + * @param {FS.File|object} selector + * @param {object} modifier + * @param {object} [options] + * @param {function} [callback] + * [Meteor docs](http://docs.meteor.com/#update) + */ +FS.Collection.prototype.update = function(selector, modifier, options, callback) { + var self = this; + if (selector instanceof FS.File) { + // Make sure the file belongs to this FS.Collection + if (selector.collectionName === self.files._name) { + return selector.update(modifier, options, callback); + } else { + // Tried to save a file in the wrong FS.Collection + throw new Error('FS.Collection cannot update file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"'); + } + } + + return self.files.update(selector, modifier, options, callback); +}; + +/** @method FS.Collection.prototype.remove Remove the file from the collection + * @public + * @param {FS.File|object} selector + * @param {Function} [callback] + * [Meteor docs](http://docs.meteor.com/#remove) + */ +FS.Collection.prototype.remove = function(selector, callback) { + var self = this; + if (selector instanceof FS.File) { + + // Make sure the file belongs to this FS.Collection + if (selector.collectionName === self.files._name) { + return selector.remove(callback); + } else { + // Tried to remove a file from the wrong FS.Collection + throw new Error('FS.Collection cannot remove file belongs to: "' + selector.collectionName + '" not: "' + self.files._name + '"'); + } + } + + //doesn't work correctly on the client without a callback + callback = callback || FS.Utility.defaultCallback; + return self.files.remove(selector, callback); +}; + +/** @method FS.Collection.prototype.findOne + * @public + * @param {[selector](http://docs.meteor.com/#selectors)} selector + * [Meteor docs](http://docs.meteor.com/#findone) + * Example: + ```js + var images = new FS.Collection( ... ); + // Get the file object + var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' }); + ``` + */ +// Call findOne on files collection +FS.Collection.prototype.findOne = function(selector) { + var self = this; + return self.files.findOne.apply(self.files, arguments); +}; + +/** @method FS.Collection.prototype.find + * @public + * @param {[selector](http://docs.meteor.com/#selectors)} selector + * [Meteor docs](http://docs.meteor.com/#find) + * Example: + ```js + var images = new FS.Collection( ... ); + // Get the all file objects + var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch(); + ``` + */ +FS.Collection.prototype.find = function(selector) { + var self = this; + return self.files.find.apply(self.files, arguments); +}; + +/** @method FS.Collection.prototype.allow + * @public + * @param {object} options + * @param {function} options.download Function that checks if the file contents may be downloaded + * @param {function} options.insert + * @param {function} options.update + * @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be allowed + * @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + * [Meteor docs](http://docs.meteor.com/#allow) + * Example: + ```js + var images = new FS.Collection( ... ); + // Get the all file objects + var files = images.allow({ + insert: function(userId, doc) { return true; }, + update: function(userId, doc, fields, modifier) { return true; }, + remove: function(userId, doc) { return true; }, + download: function(userId, fileObj) { return true; }, + }); + ``` + */ +FS.Collection.prototype.allow = function(options) { + var self = this; + + // Pull out the custom "download" functions + if (options.download) { + if (!(options.download instanceof Function)) { + throw new Error("allow: Value for `download` must be a function"); + } + self._validators.download.allow.push(options.download); + delete options.download; + } + + return self.files.allow.call(self.files, options); +}; + +/** @method FS.Collection.prototype.deny + * @public + * @param {object} options + * @param {function} options.download Function that checks if the file contents may be downloaded + * @param {function} options.insert + * @param {function} options.update + * @param {function} options.remove Functions that look at a proposed modification to the database and return true if it should be denyed + * @param {[string]} [options.fetch] Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + * [Meteor docs](http://docs.meteor.com/#deny) + * Example: + ```js + var images = new FS.Collection( ... ); + // Get the all file objects + var files = images.deny({ + insert: function(userId, doc) { return true; }, + update: function(userId, doc, fields, modifier) { return true; }, + remove: function(userId, doc) { return true; }, + download: function(userId, fileObj) { return true; }, + }); + ``` + */ +FS.Collection.prototype.deny = function(options) { + var self = this; + + // Pull out the custom "download" functions + if (options.download) { + if (!(options.download instanceof Function)) { + throw new Error("deny: Value for `download` must be a function"); + } + self._validators.download.deny.push(options.download); + delete options.download; + } + + return self.files.deny.call(self.files, options); +}; + +// TODO: Upsert? + +/** + * We provide a default implementation that doesn't do anything. + * Can be changed by user or packages, such as the default cfs-collection-filters pkg. + * @param {FS.File} fileObj File object + * @return {Boolean} Should we allow insertion of this file? + */ +FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) { + return true; +}; diff --git a/packages/wekan-cfs-collection/api.md b/packages/wekan-cfs-collection/api.md new file mode 100644 index 000000000..01a005090 --- /dev/null +++ b/packages/wekan-cfs-collection/api.md @@ -0,0 +1,180 @@ +## cfs-collection Public API ## + +CollectionFS, FS.Collection object + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +- + +### *fsCollection*.insert(fileRef, [callback])  Anywhere ### + +*This method __insert__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}* + + File data reference + +* __callback__ *{function}* (Optional) + + Callback `function(error, fileObj)` + + +__Returns__ *{FS.File}* +The `file object` +[Meteor docs](http://docs.meteor.com/#insert) + +> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8) + + +- + +### *fsCollection*.update(selector, modifier, [options], [callback])  Anywhere ### + +*This method __update__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[FS.File](#FS.File)|object}* +* __modifier__ *{object}* +* __options__ *{object}* (Optional) +* __callback__ *{function}* (Optional) +[Meteor docs](http://docs.meteor.com/#update) + +> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71) + + +- + +### *fsCollection*.remove(selector, [callback])  Anywhere ### + +*This method __remove__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[FS.File](#FS.File)|object}* +* __callback__ *{Function}* (Optional) +[Meteor docs](http://docs.meteor.com/#remove) + +> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92) + + +- + +### *fsCollection*.findOne(selector)  Anywhere ### + +*This method __findOne__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[selector](http://docs.meteor.com/#selectors)}* +[Meteor docs](http://docs.meteor.com/#findone) +Example: +```js +var images = new FS.Collection( ... ); +// Get the file object +var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' }); +``` + +> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122) + + +- + +### *fsCollection*.find(selector)  Anywhere ### + +*This method __find__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[selector](http://docs.meteor.com/#selectors)}* +[Meteor docs](http://docs.meteor.com/#find) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch(); +``` + +> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138) + + +- + +### *fsCollection*.allow(options)  Anywhere ### + +*This method __allow__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __options__ *{object}* + * __download__ *{function}* + + Function that checks if the file contents may be downloaded + + * __insert__ *{function}* + * __update__ *{function}* + * __remove__ *{function}* + + Functions that look at a proposed modification to the database and return true if it should be allowed + + * __fetch__ *{[string]}* (Optional) + + Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + +[Meteor docs](http://docs.meteor.com/#allow) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.allow({ +insert: function(userId, doc) { return true; }, +update: function(userId, doc, fields, modifier) { return true; }, +remove: function(userId, doc) { return true; }, +download: function(userId, fileObj) { return true; }, +}); +``` + +> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164) + + +- + +### *fsCollection*.deny(options)  Anywhere ### + +*This method __deny__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __options__ *{object}* + * __download__ *{function}* + + Function that checks if the file contents may be downloaded + + * __insert__ *{function}* + * __update__ *{function}* + * __remove__ *{function}* + + Functions that look at a proposed modification to the database and return true if it should be denyed + + * __fetch__ *{[string]}* (Optional) + + Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + +[Meteor docs](http://docs.meteor.com/#deny) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.deny({ +insert: function(userId, doc) { return true; }, +update: function(userId, doc, fields, modifier) { return true; }, +remove: function(userId, doc) { return true; }, +download: function(userId, fileObj) { return true; }, +}); +``` + +> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200) + + diff --git a/packages/wekan-cfs-collection/common.js b/packages/wekan-cfs-collection/common.js new file mode 100644 index 000000000..7e9942261 --- /dev/null +++ b/packages/wekan-cfs-collection/common.js @@ -0,0 +1,171 @@ +/** + * + * @constructor + * @param {string} name A name for the collection + * @param {Object} options + * @param {FS.StorageAdapter[]} options.stores An array of stores in which files should be saved. At least one is required. + * @param {Object} [options.filter] Filter definitions + * @param {Number} [options.chunkSize=2MB] Override the chunk size in bytes for uploads + * @param {Function} [options.uploader] A function to pass FS.File instances after inserting, which will begin uploading them. By default, `FS.HTTP.uploadQueue.uploadFile` is used if the `cfs-upload-http` package is present, or `FS.DDP.uploadQueue.uploadFile` is used if the `cfs-upload-ddp` package is present. You can override with your own, or set to `null` to prevent automatic uploading. + * @returns {undefined} + */ +FS.Collection = function(name, options) { + var self = this; + + self.storesLookup = {}; + + self.primaryStore = {}; + + self.options = { + filter: null, //optional + stores: [], //required + chunkSize: null + }; + + // Define a default uploader based on which upload packages are present, + // preferring HTTP. You may override with your own function or + // set to null to skip automatic uploading of data after file insert/update. + if (FS.HTTP && FS.HTTP.uploadQueue) { + self.options.uploader = FS.HTTP.uploadQueue.uploadFile; + } else if (FS.DDP && FS.DDP.uploadQueue) { + self.options.uploader = FS.DDP.uploadQueue.uploadFile; + } + + // Extend and overwrite options + FS.Utility.extend(self.options, options || {}); + + // Set the FS.Collection name + self.name = name; + + // Make sure at least one store has been supplied. + // Usually the stores aren't used on the client, but we need them defined + // so that we can access their names and use the first one as the default. + if (FS.Utility.isEmpty(self.options.stores)) { + throw new Error("You must specify at least one store. Please consult the documentation."); + } + + FS.Utility.each(self.options.stores, function(store, i) { + // Set the primary store + if (i === 0) { + self.primaryStore = store; + } + + // Check for duplicate naming + if (typeof self.storesLookup[store.name] !== 'undefined') { + throw new Error('FS.Collection store names must be uniq, duplicate found: ' + store.name); + } + + // Set the lookup + self.storesLookup[store.name] = store; + + if (Meteor.isServer) { + + // Emit events based on store events + store.on('stored', function (storeName, fileObj) { + // This is weird, but currently there is a bug where each store will emit the + // events for all other stores, too, so we need to make sure that this event + // is truly for this store. + if (storeName !== store.name) + return; + // When a file is successfully stored into the store, we emit a "stored" event on the FS.Collection only if the file belongs to this collection + if (fileObj.collectionName === name) { + var emitted = self.emit('stored', fileObj, store.name); + if (FS.debug && !emitted) { + console.log(fileObj.name({store: store.name}) + ' was successfully saved to the ' + store.name + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on the ' + name + ' collection.'); + } + } + fileObj.emit('stored', store.name); + }); + + store.on('error', function (storeName, error, fileObj) { + // This is weird, but currently there is a bug where each store will emit the + // events for all other stores, too, so we need to make sure that this event + // is truly for this store. + if (storeName !== store.name) + return; + // When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection + if (fileObj.collectionName === name) { + error = new Error('Error storing file to the ' + store.name + ' store: ' + error.message); + var emitted = self.emit('error', error, fileObj, store.name); + if (FS.debug && !emitted) { + console.log(error.message); + } + } + fileObj.emit('error', store.name); + }); + + } + }); + + var _filesOptions = { + transform: function(doc) { + // This should keep the filerecord in the file object updated in reactive + // context + var result = new FS.File(doc, true); + result.collectionName = name; + return result; + } + }; + + if(self.options.idGeneration) _filesOptions.idGeneration = self.options.idGeneration; + + // Enable specifying an alternate driver, to change where the filerecord is stored + // Drivers can be created with MongoInternals.RemoteCollectionDriver() + if(self.options._driver){ + _filesOptions._driver = self.options._driver; + } + + // Create the 'cfs.' ++ ".filerecord" and use fsFile + var collectionName = 'cfs.' + name + '.filerecord'; + self.files = new Mongo.Collection(collectionName, _filesOptions); + + // For storing custom allow/deny functions + self._validators = { + download: {allow: [], deny: []} + }; + + // Set up filters + // XXX Should we deprecate the filter option now that this is done with a separate pkg, or just keep it? + if (self.filters) { + self.filters(self.options.filter); + } + + // Save the collection reference (we want it without the 'cfs.' prefix and '.filerecord' suffix) + FS._collections[name] = this; + + // Set up observers + Meteor.isServer && FS.FileWorker && FS.FileWorker.observe(this); + + // Emit "removed" event on collection + self.files.find().observe({ + removed: function(fileObj) { + self.emit('removed', fileObj); + } + }); + + // Emit events based on TempStore events + if (FS.TempStore) { + FS.TempStore.on('stored', function (fileObj, result) { + // When a file is successfully stored into the temp store, we emit an "uploaded" event on the FS.Collection only if the file belongs to this collection + if (fileObj.collectionName === name) { + var emitted = self.emit('uploaded', fileObj); + if (FS.debug && !emitted) { + console.log(fileObj.name() + ' was successfully uploaded. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "uploaded" event on the ' + name + ' collection.'); + } + } + }); + + FS.TempStore.on('error', function (error, fileObj) { + // When a file has an error while being stored into the temp store, we emit an "error" event on the FS.Collection only if the file belongs to this collection + if (fileObj.collectionName === name) { + self.emit('error', new Error('Error storing uploaded file to TempStore: ' + error.message), fileObj); + } + }); + } else if (Meteor.isServer) { + throw new Error("FS.Collection constructor: FS.TempStore must be defined before constructing any FS.Collections.") + } + +}; + +// An FS.Collection can emit events +FS.Collection.prototype = new EventEmitter(); diff --git a/packages/wekan-cfs-collection/internal.api.md b/packages/wekan-cfs-collection/internal.api.md new file mode 100644 index 000000000..6a9708fac --- /dev/null +++ b/packages/wekan-cfs-collection/internal.api.md @@ -0,0 +1,390 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["common.js"](common.js) Where: {client|server}__ + +*** + +############################################################################# + +COLLECTION FS + +############################################################################# +- + +### new *fs*.Collection(name, options)  Anywhere ### + +*This method __Collection__ is defined in `FS`* + +__Arguments__ + +* __name__ *{string}* + + A name for the collection + +* __options__ *{Object}* + * __stores__ *{[FS.StorageAdapter[]](#FS.StorageAdapter[])}* + + An array of stores in which files should be saved. At least one is required. + + * __filter__ *{Object}* (Optional) + + Filter definitions + + * __chunkSize__ *{Number}* (Optional, Default = 131072) + + Override the chunk size in bytes for uploads and downloads + + +__Returns__ *{undefined}* + + + +> ```FS.Collection = function(name, options) { ...``` [common.js:17](common.js#L17) + + +*** + +__File: ["api.common.js"](api.common.js) Where: {client|server}__ + +*** + +### *fsCollection*.insert(fileRef, [callback])  Anywhere ### + +*This method __insert__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __fileRef__ *{[FS.File](#FS.File)|[File](#File)}* + + File data reference + +* __callback__ *{function}* (Optional) + + Callback `function(error, fileObj)` + + +__Returns__ *{FS.File}* +The `file object` +[Meteor docs](http://docs.meteor.com/#insert) + +> ```FS.Collection.prototype.insert = function(fileRef, callback) { ...``` [api.common.js:8](api.common.js#L8) + + +- + +### *fsCollection*.update(selector, modifier, [options], [callback])  Anywhere ### + +*This method __update__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[FS.File](#FS.File)|object}* +* __modifier__ *{object}* +* __options__ *{object}* (Optional) +* __callback__ *{function}* (Optional) +[Meteor docs](http://docs.meteor.com/#update) + +> ```FS.Collection.prototype.update = function(selector, modifier, options, callback) { ...``` [api.common.js:71](api.common.js#L71) + + +- + +### *fsCollection*.remove(selector, [callback])  Anywhere ### + +*This method __remove__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[FS.File](#FS.File)|object}* +* __callback__ *{Function}* (Optional) +[Meteor docs](http://docs.meteor.com/#remove) + +> ```FS.Collection.prototype.remove = function(selector, callback) { ...``` [api.common.js:92](api.common.js#L92) + + +- + +### *fsCollection*.findOne(selector)  Anywhere ### + +*This method __findOne__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[selector](http://docs.meteor.com/#selectors)}* +[Meteor docs](http://docs.meteor.com/#findone) +Example: +```js +var images = new FS.Collection( ... ); +// Get the file object +var fo = images.findOne({ _id: 'NpnskCt6ippN6CgD8' }); +``` + +> ```FS.Collection.prototype.findOne = function(selector) { ...``` [api.common.js:122](api.common.js#L122) + + +- + +### *fsCollection*.find(selector)  Anywhere ### + +*This method __find__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __selector__ *{[selector](http://docs.meteor.com/#selectors)}* +[Meteor docs](http://docs.meteor.com/#find) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.find({ _id: 'NpnskCt6ippN6CgD8' }).fetch(); +``` + +> ```FS.Collection.prototype.find = function(selector) { ...``` [api.common.js:138](api.common.js#L138) + + +- + +### *fsCollection*.allow(options)  Anywhere ### + +*This method __allow__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __options__ *{object}* + * __download__ *{function}* + + Function that checks if the file contents may be downloaded + + * __insert__ *{function}* + * __update__ *{function}* + * __remove__ *{function}* + + Functions that look at a proposed modification to the database and return true if it should be allowed + + * __fetch__ *{[string]}* (Optional) + + Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + +[Meteor docs](http://docs.meteor.com/#allow) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.allow({ +insert: function(userId, doc) { return true; }, +update: function(userId, doc, fields, modifier) { return true; }, +remove: function(userId, doc) { return true; }, +download: function(userId, fileObj) { return true; }, +}); +``` + +> ```FS.Collection.prototype.allow = function(options) { ...``` [api.common.js:164](api.common.js#L164) + + +- + +### *fsCollection*.deny(options)  Anywhere ### + +*This method __deny__ is defined in `prototype` of `FS.Collection`* + +__Arguments__ + +* __options__ *{object}* + * __download__ *{function}* + + Function that checks if the file contents may be downloaded + + * __insert__ *{function}* + * __update__ *{function}* + * __remove__ *{function}* + + Functions that look at a proposed modification to the database and return true if it should be denyed + + * __fetch__ *{[string]}* (Optional) + + Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your update and remove functions + +[Meteor docs](http://docs.meteor.com/#deny) +Example: +```js +var images = new FS.Collection( ... ); +// Get the all file objects +var files = images.deny({ +insert: function(userId, doc) { return true; }, +update: function(userId, doc, fields, modifier) { return true; }, +remove: function(userId, doc) { return true; }, +download: function(userId, fileObj) { return true; }, +}); +``` + +> ```FS.Collection.prototype.deny = function(options) { ...``` [api.common.js:200](api.common.js#L200) + + +*** + +__File: ["api.client.js"](api.client.js) Where: {client}__ + +*** + +### _eventCallback(templateName, selector, dataContext, evt, temp, fsFile)  Client ### + +*This method is private* + +__Arguments__ + +* __templateName__ *{string}* + + Name of template to apply events on + +* __selector__ *{string}* + + The element selector eg. "#uploadField" + +* __dataContext__ *{object}* + + The event datacontext + +* __evt__ *{object}* + + The event object { error, file } + +* __temp__ *{object}* + + The template instance + +* __fsFile__ *{[FS.File](#FS.File)}* + + File that triggered the event + + +> ```var _eventCallback = function fsEventCallback(templateName, selector, dataContext, evt, temp, fsFile) { ...``` [api.client.js:10](api.client.js#L10) + + +- + +### _eachFile(files, metadata, callback)  Client ### + +*This method is private* + +__Arguments__ + +* __files__ *{array}* + + List of files to iterate over + +* __metadata__ *{object}* + + Data to attach to the files + +* __callback__ *{function}* + + Function to pass the prepared `FS.File` object + + +> ```var _eachFile = function(files, metadata, callback) { ...``` [api.client.js:36](api.client.js#L36) + + +- + +### *fsCollection*.acceptDropsOn(templateName, selector, [metadata])  Client ### + +*This method __acceptDropsOn__ is defined in `FS.Collection`* + +__Arguments__ + +* __templateName__ *{string}* + + Name of template to apply events on + +* __selector__ *{string}* + + The element selector eg. "#uploadField" + +* __metadata__ *{object|function}* (Optional) + + Data/getter to attach to the file objects + + +Using this method adds an `uploaded` and `uploadFailed` event to the +template events. The event object contains `{ error, file }` + +Example: +```css +.dropzone { +border: 2px dashed silver; +height: 5em; +padding-top: 3em; +-webkit-border-radius: 8px; +-moz-border-radius: 8px; +-ms-border-radius: 8px; +-o-border-radius: 8px; +border-radius: 8px; +} +``` +```html + +``` +```js +Template.hello.events({ +'uploaded #dropzone': function(event, temp) { +console.log('Event Uploaded: ' + event.file._id); +} +}); +images.acceptDropsOn('hello', '#dropzone'); +``` + +> ```FS.Collection.prototype.acceptDropsOn = function(templateName, selector, metadata) { ...``` [api.client.js:99](api.client.js#L99) + + +- + +### *fsCollection*.acceptUploadFrom(templateName, selector, [metadata])  Client ### + +*This method __acceptUploadFrom__ is defined in `FS.Collection`* + +__Arguments__ + +* __templateName__ *{string}* + + Name of template to apply events on + +* __selector__ *{string}* + + The element selector eg. "#uploadField" + +* __metadata__ *{object|function}* (Optional) + + Data/getter to attach to the file objects + + +Using this method adds an `uploaded` and `uploadFailed` event to the +template events. The event object contains `{ error, file }` + +Example: +```html + +``` +```js +Template.hello.events({ +'uploaded #files': function(event, temp) { +console.log('Event Uploaded: ' + event.file._id); +} +}); +images.acceptUploadFrom('hello', '#files'); +``` + +> ```FS.Collection.prototype.acceptUploadFrom = function(templateName, selector, metadata) { ...``` [api.client.js:156](api.client.js#L156) + + diff --git a/packages/wekan-cfs-collection/package.js b/packages/wekan-cfs-collection/package.js new file mode 100644 index 000000000..1af88d564 --- /dev/null +++ b/packages/wekan-cfs-collection/package.js @@ -0,0 +1,43 @@ +Package.describe({ + name: 'wekan-cfs-collection', + version: '0.5.5', + summary: 'CollectionFS, FS.Collection object', + git: 'https://github.com/zcfs/Meteor-cfs-collection.git' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use([ + // CFS + 'wekan-cfs-base-package@0.0.30', + 'wekan-cfs-tempstore@0.1.4', + // Core + 'deps', + 'check', + 'livedata', + 'mongo-livedata', + // Other + 'raix:eventemitter@0.1.1' + ]); + + // Weak dependencies for uploaders + api.use(['wekan-cfs-upload-http@0.0.20', 'wekan-cfs-upload-ddp@0.0.17'], { weak: true }); + + api.addFiles([ + 'common.js', + 'api.common.js' + ], 'client'); + + api.addFiles([ + 'common.js', + 'api.common.js' + ], 'server'); +}); + +Package.onTest(function (api) { + api.use(['wekan-cfs-standard-packages', 'wekan-cfs-gridfs', 'tinytest', 'underscore', 'test-helpers']); + + api.addFiles('tests/server-tests.js', 'server'); + api.addFiles('tests/client-tests.js', 'client'); +}); diff --git a/packages/wekan-cfs-collection/tests/client-tests.js b/packages/wekan-cfs-collection/tests/client-tests.js new file mode 100644 index 000000000..bf29338c7 --- /dev/null +++ b/packages/wekan-cfs-collection/tests/client-tests.js @@ -0,0 +1,33 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-collection - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +/* + * TODO FS.Collection Client Tests + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-collection/tests/server-tests.js b/packages/wekan-cfs-collection/tests/server-tests.js new file mode 100644 index 000000000..3f6297022 --- /dev/null +++ b/packages/wekan-cfs-collection/tests/server-tests.js @@ -0,0 +1,96 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +var fileCollection = new FS.Collection('files', { + stores: [ + new FS.Store.GridFS('files') + ] +}); + +Tinytest.add('cfs-collection - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +Tinytest.add('cfs-collection - insert URL - sync - one', function (test) { + // XXX should switch to a local URL we host + + // One + try { + fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg"); + test.isTrue(true); + } catch (err) { + test.isFalse(!!err, "URL insert failed"); + } + +}); + +Tinytest.add('cfs-collection - insert URL - sync - loop', function (test) { + // XXX should switch to a local URL we host + + try { + for (var i = 0, len = 10; i < len; i++) { + fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg"); + } + test.isTrue(true); + } catch (err) { + test.isFalse(!!err, "URL insert failed"); + } +}); + +Tinytest.addAsync('cfs-collection - insert URL - async - one', function (test, next) { + // XXX should switch to a local URL we host + + // One + try { + fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) { + test.isNull(error); + test.instanceOf(result, FS.File); + next(); + }); + } catch (err) { + test.isFalse(!!err, "URL insert failed"); + next(); + } +}); + +Tinytest.addAsync('cfs-collection - insert URL - async - loop', function (test, next) { + // XXX should switch to a local URL we host + + try { + var done = 0; + for (var i = 0, len = 10; i < len; i++) { + fileCollection.insert("http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg", function (error, result) { + test.isNull(error); + test.instanceOf(result, FS.File); + done++; + if (done === 10) { + next(); + } + }); + } + } catch (err) { + test.isFalse(!!err, "URL insert failed"); + next(); + } +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-data-man/.travis.yml b/packages/wekan-cfs-data-man/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-data-man/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-data-man/LICENSE.md b/packages/wekan-cfs-data-man/LICENSE.md new file mode 100644 index 000000000..f0f70cafa --- /dev/null +++ b/packages/wekan-cfs-data-man/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com, and [@aldeed](https://github.com/aldeed), aka Eric Dobbertin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-data-man/README.md b/packages/wekan-cfs-data-man/README.md new file mode 100644 index 000000000..a124f46e3 --- /dev/null +++ b/packages/wekan-cfs-data-man/README.md @@ -0,0 +1,8 @@ +wekan-cfs-data-man [![Build Status](https://travis-ci.org/CollectionFS/Meteor-data-man.svg?branch=master)](https://travis-ci.org/CollectionFS/Meteor-data-man) +========================= + +Who can handle your arbitrary data? The DataMan can. + +This is a package used by CollectionFS to attach data to file objects. Could be used for other things, though. + +[Public API](api.md) diff --git a/packages/wekan-cfs-data-man/api.md b/packages/wekan-cfs-data-man/api.md new file mode 100644 index 000000000..1e29ebe07 --- /dev/null +++ b/packages/wekan-cfs-data-man/api.md @@ -0,0 +1,309 @@ +## data-man Public API ## + +A data manager, allowing you to attach various types of data and get it back in various other types + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +- + +### new DataMan(data, [type])  Client ### + + +__Arguments__ + +* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}* + + The data that you want to manipulate. + +* __type__ *{String}* (Optional) + + The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL + + + +> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8) + + +- + +### *dataman*.getBlob(callback)  Client ### + +*This method __getBlob__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{Function}* + + callback(error, blob) + + +__Returns__ *{undefined}* + + +Passes a Blob representing this data to a callback. + +> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52) + + +- + +### *dataman*.getBinary([start], [end], callback)  Client ### + +*This method __getBinary__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __start__ *{Number}* (Optional) + + First byte position to read. + +* __end__ *{Number}* (Optional) + + Last byte position to read. + +* __callback__ *{Function}* + + callback(error, binaryData) + + +__Returns__ *{undefined}* + + +Passes a Uint8Array representing this data to a callback. + +> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84) + + +- + +### *dataman*.saveAs([filename])  Client ### + +*This method __saveAs__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __filename__ *{String}* (Optional) + +__Returns__ *{undefined}* + +Tells the browser to save the data like a normal downloaded file, +using the provided filename. + + +> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146) + + +- + +### *dataman*.getDataUri(callback)  Client ### + +*This method __getDataUri__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, dataUri) + + + +> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166) + + +- + +### *dataman*.type()  Client ### + +*This method __type__ is defined in `prototype` of `DataMan`* + + +Returns the type of the data. + +> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227) + + +- + +### new DataMan(data, [type])  Server ### + + +__Arguments__ + +* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}* + + The data that you want to manipulate. + +* __type__ *{String}* (Optional) + + The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL + + + +> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10) + + +- + +### *dataman*.getBuffer([callback])  Server ### + +*This method __getBuffer__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, buffer) + + +__Returns__ *{Buffer|undefined}* + + +Returns a Buffer representing this data, or passes the Buffer to a callback. + +> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54) + + +- + +### *dataman*.saveToFile()  Server ### + +*This method __saveToFile__ is defined in `prototype` of `DataMan`* + +__Returns__ *{undefined}* + + +Saves this data to a filepath on the local filesystem. + +> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66) + + +- + +### *dataman*.getDataUri([callback])  Server ### + +*This method __getDataUri__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, dataUri) + + + +If no callback, returns the data URI. + +> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84) + + +- + +### *dataman*.createReadStream()  Server ### + +*This method __createReadStream__ is defined in `prototype` of `DataMan`* + + +Returns a read stream for the data. + +> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95) + + +- + +### *dataman*.size([callback])  Server ### + +*This method __size__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, size) + + + +If no callback, returns the size in bytes of the data. + +> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106) + + +- + +### *dataman*.type()  Server ### + +*This method __type__ is defined in `prototype` of `DataMan`* + + +Returns the type of the data. + +> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117) + + +- + +### new *dataman*.Buffer(buffer, type)  Server ### + +*This method __Buffer__ is defined in `DataMan`* + +__Arguments__ + +* __buffer__ *{Buffer}* +* __type__ *{String}* + + The data content (MIME) type. + + + +> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10) + + +- + +### new *dataman*.DataURI(dataUri)  Server ### + +*This method __DataURI__ is defined in `DataMan`* + +__Arguments__ + +* __dataUri__ *{String}* + + +> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7) + + +- + +### new *dataman*.FilePath(filepath, [type])  Server ### + +*This method __FilePath__ is defined in `DataMan`* + +__Arguments__ + +* __filepath__ *{String}* +* __type__ *{String}* (Optional) + + The data content (MIME) type. Will lookup from file if not passed. + + + +> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11) + + +- + +### new *dataman*.URL(url, type)  Server ### + +*This method __URL__ is defined in `DataMan`* + +__Arguments__ + +* __url__ *{String}* +* __type__ *{String}* + + The data content (MIME) type. + + + +> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10) + + diff --git a/packages/wekan-cfs-data-man/client/Blob.js b/packages/wekan-cfs-data-man/client/Blob.js new file mode 100644 index 000000000..7e5e42de7 --- /dev/null +++ b/packages/wekan-cfs-data-man/client/Blob.js @@ -0,0 +1,166 @@ +/* Blob.js + * A Blob implementation. + * 2013-12-27 + * + * By Eli Grey, http://eligrey.com + * By Devin Samarin, https://github.com/eboyjr + * License: X11/MIT + * See LICENSE.md + */ + +/*global self, unescape */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ + +if (!(typeof Blob === "function" || typeof Blob === "object") || typeof URL === "undefined") +if ((typeof Blob === "function" || typeof Blob === "object") && typeof webkitURL !== "undefined") self.URL = webkitURL; +else var Blob = (function (view) { + "use strict"; + + var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) { + var + get_class = function(object) { + return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; + } + , FakeBlobBuilder = function BlobBuilder() { + this.data = []; + } + , FakeBlob = function Blob(data, type, encoding) { + this.data = data; + this.size = data.length; + this.type = type; + this.encoding = encoding; + } + , FBB_proto = FakeBlobBuilder.prototype + , FB_proto = FakeBlob.prototype + , FileReaderSync = view.FileReaderSync + , FileException = function(type) { + this.code = this[this.name = type]; + } + , file_ex_codes = ( + "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" + ).split(" ") + , file_ex_code = file_ex_codes.length + , real_URL = view.URL || view.webkitURL || view + , real_create_object_URL = real_URL.createObjectURL + , real_revoke_object_URL = real_URL.revokeObjectURL + , URL = real_URL + , btoa = view.btoa + , atob = view.atob + + , ArrayBuffer = view.ArrayBuffer + , Uint8Array = view.Uint8Array + ; + FakeBlob.fake = FB_proto.fake = true; + while (file_ex_code--) { + FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; + } + if (!real_URL.createObjectURL) { + URL = view.URL = {}; + } + URL.createObjectURL = function(blob) { + var + type = blob.type + , data_URI_header + ; + if (type === null) { + type = "application/octet-stream"; + } + if (blob instanceof FakeBlob) { + data_URI_header = "data:" + type; + if (blob.encoding === "base64") { + return data_URI_header + ";base64," + blob.data; + } else if (blob.encoding === "URI") { + return data_URI_header + "," + decodeURIComponent(blob.data); + } if (btoa) { + return data_URI_header + ";base64," + btoa(blob.data); + } else { + return data_URI_header + "," + encodeURIComponent(blob.data); + } + } else if (real_create_object_URL) { + return real_create_object_URL.call(real_URL, blob); + } + }; + URL.revokeObjectURL = function(object_URL) { + if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { + real_revoke_object_URL.call(real_URL, object_URL); + } + }; + FBB_proto.append = function(data/*, endings*/) { + var bb = this.data; + // decode data to a binary string + if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { + var + str = "" + , buf = new Uint8Array(data) + , i = 0 + , buf_len = buf.length + ; + for (; i < buf_len; i++) { + str += String.fromCharCode(buf[i]); + } + bb.push(str); + } else if (get_class(data) === "Blob" || get_class(data) === "File") { + if (FileReaderSync) { + var fr = new FileReaderSync; + bb.push(fr.readAsBinaryString(data)); + } else { + // async FileReader won't work as BlobBuilder is sync + throw new FileException("NOT_READABLE_ERR"); + } + } else if (data instanceof FakeBlob) { + if (data.encoding === "base64" && atob) { + bb.push(atob(data.data)); + } else if (data.encoding === "URI") { + bb.push(decodeURIComponent(data.data)); + } else if (data.encoding === "raw") { + bb.push(data.data); + } + } else { + if (typeof data !== "string") { + data += ""; // convert unsupported types to strings + } + // decode UTF-16 to binary string + bb.push(unescape(encodeURIComponent(data))); + } + }; + FBB_proto.getBlob = function(type) { + if (!arguments.length) { + type = null; + } + return new FakeBlob(this.data.join(""), type, "raw"); + }; + FBB_proto.toString = function() { + return "[object BlobBuilder]"; + }; + FB_proto.slice = function(start, end, type) { + var args = arguments.length; + if (args < 3) { + type = null; + } + return new FakeBlob( + this.data.slice(start, args > 1 ? end : this.data.length) + , type + , this.encoding + ); + }; + FB_proto.toString = function() { + return "[object Blob]"; + }; + return FakeBlobBuilder; + }(view)); + + return function Blob(blobParts, options) { + var type = options ? (options.type || "") : ""; + var builder = new BlobBuilder(); + if (blobParts) { + for (var i = 0, len = blobParts.length; i < len; i++) { + builder.append(blobParts[i]); + } + } + return builder.getBlob(type); + }; +}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); diff --git a/packages/wekan-cfs-data-man/client/data-man-api.js b/packages/wekan-cfs-data-man/client/data-man-api.js new file mode 100644 index 000000000..709fba654 --- /dev/null +++ b/packages/wekan-cfs-data-man/client/data-man-api.js @@ -0,0 +1,302 @@ +/** + * @method DataMan + * @public + * @constructor + * @param {File|Blob|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate. + * @param {String} [type] The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL + */ +DataMan = function DataMan(data, type) { + var self = this; + + if (!data) { + throw new Error("DataMan constructor requires a data argument"); + } + + // The end result of all this is that we will have one of the following set: + // - self.blob + // - self.url + // Unless we already have in-memory data, we don't load anything into memory + // and instead rely on obtaining a read stream when the time comes. + if (typeof File !== "undefined" && data instanceof File) { + self.blob = data; // File inherits from Blob so this is OK + self._type = data.type; + } else if (typeof Blob !== "undefined" && data instanceof Blob) { + self.blob = data; + self._type = data.type; + } else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer || EJSON.isBinary(data)) { + if (typeof Blob === "undefined") { + throw new Error("Browser must support Blobs to handle an ArrayBuffer or Uint8Array"); + } + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer or Uint8Array"); + } + self.blob = new Blob([data], {type: type}); + self._type = type; + } else if (typeof data === "string") { + if (data.slice(0, 5) === "data:") { + self._type = data.slice(5, data.indexOf(';')); + self.blob = dataURItoBlob(data, self._type); + } else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") { + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed a URL"); + } + self.url = data; + self._type = type; + } else { + throw new Error("DataMan constructor received unrecognized data string"); + } + } else { + throw new Error("DataMan constructor received data that it doesn't support"); + } +}; + +/** + * @method DataMan.prototype.getBlob + * @public + * @param {Function} [callback] - callback(error, blob) + * @returns {undefined|Blob} + * + * Passes a Blob representing this data to a callback or returns + * the Blob if no callback is provided. A callback is required + * if getting a Blob for a URL. + */ +DataMan.prototype.getBlob = function dataManGetBlob(callback) { + var self = this; + + if (callback) { + if (self.blob) { + callback(null, self.blob); + } else if (self.url) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', self.url, true); + xhr.responseType = "blob"; + xhr.onload = function(data) { + self.blob = xhr.response; + callback(null, self.blob); + }; + xhr.onerror = function(err) { + callback(err); + }; + xhr.send(); + } + } else { + if (self.url) + throw new Error('DataMan.getBlob requires a callback when managing a URL'); + return self.blob; + } +}; + +/** + * @method DataMan.prototype.getBinary + * @public + * @param {Number} [start] - First byte position to read. + * @param {Number} [end] - Last byte position to read. + * @param {Function} callback - callback(error, binaryData) + * @returns {undefined} + * + * Passes a Uint8Array representing this data to a callback. + */ +DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { + var self = this; + + if (typeof start === "function") { + callback = start; + } + callback = callback || defaultCallback; + + function read(blob) { + if (typeof FileReader === "undefined") { + callback(new Error("Browser does not support FileReader")); + return; + } + + var reader = new FileReader(); + reader.onload = function(evt) { + callback(null, new Uint8Array(evt.target.result)); + }; + reader.onerror = function(err) { + callback(err); + }; + reader.readAsArrayBuffer(blob); + } + + self.getBlob(function (error, blob) { + if (error) { + callback(error); + } else { + if (typeof start === "number" && typeof end === "number") { + var size = blob.size; + // Return the requested chunk of binary data + if (start >= size) { + callback(new Error("DataMan.getBinary: start position beyond end of data (" + size + ")")); + return; + } + end = Math.min(size, end); + + var slice = blob.slice || blob.webkitSlice || blob.mozSlice; + if (typeof slice === 'undefined') { + callback(new Error('Browser does not support File.slice')); + return; + } + + read(slice.call(blob, start, end, self._type)); + } else { + // Return the entire binary data + read(blob); + } + } + }); + +}; + +/** @method DataMan.prototype.saveAs + * @public + * @param {String} [filename] + * @return {undefined} + * + * Tells the browser to save the data like a normal downloaded file, + * using the provided filename. + * + */ +DataMan.prototype.saveAs = function dataManSaveAs(filename) { + var self = this; + + if (typeof window === "undefined") + throw new Error("window must be defined to use saveLocal"); + + if (!window.saveAs) { + console.warn('DataMan.saveAs: window.saveAs not supported by this browser - add cfs-filesaver package'); + return; + } + + self.getBlob(function (error, blob) { + if (error) { + throw error; + } else { + window.saveAs(blob, filename); + } + }); +}; + +/** + * @method DataMan.prototype.getDataUri + * @public + * @param {function} callback callback(err, dataUri) + */ +DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { + // XXX: We could consider using: URL.createObjectURL(blob); + // This will create a reference to the blob data instead of a clone + // This is part of the File API - as the rest - Not sure how to generally + // support from IE10, FF26, Chrome 31, safari 7, opera 19, ios 6, android 4 + + var self = this; + + if (typeof callback !== 'function') + throw new Error("getDataUri requires callback function"); + + if (typeof FileReader === "undefined") { + callback(new Error("Browser does not support FileReader")); + return; + } + + var fileReader = new FileReader(); + fileReader.onload = function(event) { + var dataUri = event.target.result; + callback(null, dataUri); + }; + fileReader.onerror = function(err) { + callback(err); + }; + + self.getBlob(function (error, blob) { + if (error) { + callback(error); + } else { + fileReader.readAsDataURL(blob); + } + }); +}; + +/** + * @method DataMan.prototype.size + * @public + * @param {function} [callback] callback(err, size) + * + * Passes the size of the data to the callback, if provided, + * or returns it. A callback is required to get the size of a URL on the client. + */ +DataMan.prototype.size = function dataManSize(callback) { + var self = this; + + if (callback) { + if (typeof self._size === "number") { + callback(null, self._size); + } else { + self.getBlob(function (error, blob) { + if (error) { + callback(error); + } else { + self._size = blob.size; + callback(null, self._size); + } + }); + } + } else { + if (self.url) { + throw new Error("On the client, DataMan.size requires a callback when getting size for a URL on the client"); + } else if (typeof self._size === "number") { + return self._size; + } else { + var blob = self.getBlob(); + self._size = blob.size; + return self._size; + } + } +}; + +/** + * @method DataMan.prototype.type + * @public + * + * Returns the type of the data. + */ +DataMan.prototype.type = function dataManType() { + return this._type; +}; + +/** + * @method dataURItoBlob + * @private + * @param {String} dataURI The data URI + * @param {String} dataTYPE The content type + * @returns {Blob} A new Blob instance + * + * Converts a data URI to a Blob. + */ +function dataURItoBlob(dataURI, dataTYPE) { + var str = atob(dataURI.split(',')[1]), array = []; + for(var i = 0; i < str.length; i++) array.push(str.charCodeAt(i)); + return new Blob([new Uint8Array(array)], {type: dataTYPE}); +} + +/** + * @method defaultCallback + * @private + * @param {Error} [err] + * @returns {undefined} + * + * Can be used as a default callback for client methods that need a callback. + * Simply throws the provided error if there is one. + */ +function defaultCallback(err) { + if (err) { + // Show gentle error if Meteor error + if (err instanceof Meteor.Error) { + console.error(err.message); + } else { + // Normal error, just throw error + throw err; + } + + } +} diff --git a/packages/wekan-cfs-data-man/internal.api.md b/packages/wekan-cfs-data-man/internal.api.md new file mode 100644 index 000000000..dd86af961 --- /dev/null +++ b/packages/wekan-cfs-data-man/internal.api.md @@ -0,0 +1,674 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["client/Blob.js"](client/Blob.js) Where: {client}__ + +*** + +### if {any}  Client ### + +``` +Blob.js +A Blob implementation. +2013-06-20 +By Eli Grey, http: +By Devin Samarin, https: +License: X11/MIT +See LICENSE.md +``` + + +> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17) + + +- + +### if {any}  Client ### + +``` +global unescape jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, +plusplus: true +``` + +> ```if ((typeof Blob !== ``` [client/Blob.js:17](client/Blob.js#L17) + + +*** + +__File: ["client/data-man-api.js"](client/data-man-api.js) Where: {client}__ + +*** + +### new DataMan(data, [type])  Client ### + + +__Arguments__ + +* __data__ *{[File](#File)|[Blob](#Blob)|ArrayBuffer|Uint8Array|String}* + + The data that you want to manipulate. + +* __type__ *{String}* (Optional) + + The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL + + + +> ```DataMan = function DataMan(data, type) { ...``` [client/data-man-api.js:8](client/data-man-api.js#L8) + + +- + +### *dataman*.getBlob(callback)  Client ### + +*This method __getBlob__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{Function}* + + callback(error, blob) + + +__Returns__ *{undefined}* + + +Passes a Blob representing this data to a callback. + +> ```DataMan.prototype.getBlob = function dataManGetBlob(callback) { ...``` [client/data-man-api.js:52](client/data-man-api.js#L52) + + +- + +### *dataman*.getBinary([start], [end], callback)  Client ### + +*This method __getBinary__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __start__ *{Number}* (Optional) + + First byte position to read. + +* __end__ *{Number}* (Optional) + + Last byte position to read. + +* __callback__ *{Function}* + + callback(error, binaryData) + + +__Returns__ *{undefined}* + + +Passes a Uint8Array representing this data to a callback. + +> ```DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) { ...``` [client/data-man-api.js:84](client/data-man-api.js#L84) + + +- + +### *dataman*.saveAs([filename])  Client ### + +*This method __saveAs__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __filename__ *{String}* (Optional) + +__Returns__ *{undefined}* + +Tells the browser to save the data like a normal downloaded file, +using the provided filename. + + +> ```DataMan.prototype.saveAs = function dataManSaveAs(filename) { ...``` [client/data-man-api.js:146](client/data-man-api.js#L146) + + +- + +### *dataman*.getDataUri(callback)  Client ### + +*This method __getDataUri__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, dataUri) + + + +> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [client/data-man-api.js:166](client/data-man-api.js#L166) + + +- + +### *dataman*.type()  Client ### + +*This method __type__ is defined in `prototype` of `DataMan`* + + +Returns the type of the data. + +> ```DataMan.prototype.type = function dataManType() { ...``` [client/data-man-api.js:227](client/data-man-api.js#L227) + + +- + +### dataURItoBlob(dataURI, dataTYPE)  undefined ### + +*This method is private* + +__Arguments__ + +* __dataURI__ *{String}* + + The data URI + +* __dataTYPE__ *{String}* + + The content type + + +__Returns__ *{Blob}* +A new Blob instance + + +Converts a data URI to a Blob. + +> ```function dataURItoBlob(dataURI, dataTYPE) { ...``` [client/data-man-api.js:240](client/data-man-api.js#L240) + + +- + +### defaultCallback([err])  undefined ### + +*This method is private* + +__Arguments__ + +* __err__ *{[Error](#Error)}* (Optional) + +__Returns__ *{undefined}* + + +Can be used as a default callback for client methods that need a callback. +Simply throws the provided error if there is one. + +> ```function defaultCallback(err) { ...``` [client/data-man-api.js:255](client/data-man-api.js#L255) + + +*** + +__File: ["server/data-man-api.js"](server/data-man-api.js) Where: {server}__ + +*** + +### new DataMan(data, [type])  Server ### + + +__Arguments__ + +* __data__ *{Buffer|ArrayBuffer|Uint8Array|String}* + + The data that you want to manipulate. + +* __type__ *{String}* (Optional) + + The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL + + + +> ```DataMan = function DataMan(data, type) { ...``` [server/data-man-api.js:10](server/data-man-api.js#L10) + + +- + +### *dataman*.getBuffer([callback])  Server ### + +*This method __getBuffer__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, buffer) + + +__Returns__ *{Buffer|undefined}* + + +Returns a Buffer representing this data, or passes the Buffer to a callback. + +> ```DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { ...``` [server/data-man-api.js:54](server/data-man-api.js#L54) + + +- + +### *dataman*.saveToFile()  Server ### + +*This method __saveToFile__ is defined in `prototype` of `DataMan`* + +__Returns__ *{undefined}* + + +Saves this data to a filepath on the local filesystem. + +> ```DataMan.prototype.saveToFile = function dataManSaveToFile(filePath) { ...``` [server/data-man-api.js:66](server/data-man-api.js#L66) + + +- + +### *dataman*.getDataUri([callback])  Server ### + +*This method __getDataUri__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, dataUri) + + + +If no callback, returns the data URI. + +> ```DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { ...``` [server/data-man-api.js:84](server/data-man-api.js#L84) + + +- + +### *dataman*.createReadStream()  Server ### + +*This method __createReadStream__ is defined in `prototype` of `DataMan`* + + +Returns a read stream for the data. + +> ```DataMan.prototype.createReadStream = function dataManCreateReadStream() { ...``` [server/data-man-api.js:95](server/data-man-api.js#L95) + + +- + +### *dataman*.size([callback])  Server ### + +*This method __size__ is defined in `prototype` of `DataMan`* + +__Arguments__ + +* __callback__ *{function}* (Optional) + + callback(err, size) + + + +If no callback, returns the size in bytes of the data. + +> ```DataMan.prototype.size = function dataManSize(callback) { ...``` [server/data-man-api.js:106](server/data-man-api.js#L106) + + +- + +### *dataman*.type()  Server ### + +*This method __type__ is defined in `prototype` of `DataMan`* + + +Returns the type of the data. + +> ```DataMan.prototype.type = function dataManType() { ...``` [server/data-man-api.js:117](server/data-man-api.js#L117) + + +*** + +__File: ["server/data-man-buffer.js"](server/data-man-buffer.js) Where: {server}__ + +*** + +### new *dataman*.Buffer(buffer, type)  Server ### + +*This method __Buffer__ is defined in `DataMan`* + +__Arguments__ + +* __buffer__ *{Buffer}* +* __type__ *{String}* + + The data content (MIME) type. + + + +> ```DataMan.Buffer = function DataManBuffer(buffer, type) { ...``` [server/data-man-buffer.js:10](server/data-man-buffer.js#L10) + + +- + +### *datamanBuffer*.getBuffer(callback)  Server ### + +*This method is private* +*This method __getBuffer__ is defined in `prototype` of `DataMan.Buffer`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, buffer) + + +__Returns__ *{Buffer|undefined}* + + +Passes a Buffer representing the data to a callback. + +> ```DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) { ...``` [server/data-man-buffer.js:24](server/data-man-buffer.js#L24) + + +- + +### *datamanBuffer*.getDataUri(callback)  Server ### + +*This method is private* +*This method __getDataUri__ is defined in `prototype` of `DataMan.Buffer`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, dataUri) + + + +Passes a data URI representing the data in the buffer to a callback. + +> ```DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) { ...``` [server/data-man-buffer.js:35](server/data-man-buffer.js#L35) + + +- + +### *datamanBuffer*.createReadStream()  Server ### + +*This method is private* +*This method __createReadStream__ is defined in `prototype` of `DataMan.Buffer`* + + +Returns a read stream for the data. + +> ```DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() { ...``` [server/data-man-buffer.js:51](server/data-man-buffer.js#L51) + + +- + +### *datamanBuffer*.size(callback)  Server ### + +*This method is private* +*This method __size__ is defined in `prototype` of `DataMan.Buffer`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, size) + + + +Passes the size in bytes of the data in the buffer to a callback. + +> ```DataMan.Buffer.prototype.size = function dataManBufferSize(callback) { ...``` [server/data-man-buffer.js:62](server/data-man-buffer.js#L62) + + +- + +### *datamanBuffer*.type()  Server ### + +*This method is private* +*This method __type__ is defined in `prototype` of `DataMan.Buffer`* + + +Returns the type of the data. + +> ```DataMan.Buffer.prototype.type = function dataManBufferType() { ...``` [server/data-man-buffer.js:80](server/data-man-buffer.js#L80) + + +*** + +__File: ["server/data-man-datauri.js"](server/data-man-datauri.js) Where: {server}__ + +*** + +### new *dataman*.DataURI(dataUri)  Server ### + +*This method __DataURI__ is defined in `DataMan`* + +__Arguments__ + +* __dataUri__ *{String}* + + +> ```DataMan.DataURI = function DataManDataURI(dataUri) { ...``` [server/data-man-datauri.js:7](server/data-man-datauri.js#L7) + + +*** + +__File: ["server/data-man-filepath.js"](server/data-man-filepath.js) Where: {server}__ + +*** + +### new *dataman*.FilePath(filepath, [type])  Server ### + +*This method __FilePath__ is defined in `DataMan`* + +__Arguments__ + +* __filepath__ *{String}* +* __type__ *{String}* (Optional) + + The data content (MIME) type. Will lookup from file if not passed. + + + +> ```DataMan.FilePath = function DataManFilePath(filepath, type) { ...``` [server/data-man-filepath.js:11](server/data-man-filepath.js#L11) + + +- + +### *datamanFilepath*.getBuffer(callback)  Server ### + +*This method is private* +*This method __getBuffer__ is defined in `prototype` of `DataMan.FilePath`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, buffer) + + +__Returns__ *{Buffer|undefined}* + + +Passes a Buffer representing the data to a callback. + +> ```DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) { ...``` [server/data-man-filepath.js:25](server/data-man-filepath.js#L25) + + +- + +### *datamanFilepath*.getDataUri(callback)  Server ### + +*This method is private* +*This method __getDataUri__ is defined in `prototype` of `DataMan.FilePath`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, dataUri) + + + +Passes a data URI representing the data to a callback. + +> ```DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) { ...``` [server/data-man-filepath.js:43](server/data-man-filepath.js#L43) + + +- + +### *datamanFilepath*.createReadStream()  Server ### + +*This method is private* +*This method __createReadStream__ is defined in `prototype` of `DataMan.FilePath`* + + +Returns a read stream for the data. + +> ```DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() { ...``` [server/data-man-filepath.js:67](server/data-man-filepath.js#L67) + + +- + +### *datamanFilepath*.size(callback)  Server ### + +*This method is private* +*This method __size__ is defined in `prototype` of `DataMan.FilePath`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, size) + + + +Passes the size in bytes of the data to a callback. + +> ```DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) { ...``` [server/data-man-filepath.js:79](server/data-man-filepath.js#L79) + + +- + +### *datamanFilepath*.type()  Server ### + +*This method is private* +*This method __type__ is defined in `prototype` of `DataMan.FilePath`* + + +Returns the type of the data. + +> ```DataMan.FilePath.prototype.type = function dataManFilePathType() { ...``` [server/data-man-filepath.js:104](server/data-man-filepath.js#L104) + + +*** + +__File: ["server/data-man-url.js"](server/data-man-url.js) Where: {server}__ + +*** + +### new *dataman*.URL(url, type)  Server ### + +*This method __URL__ is defined in `DataMan`* + +__Arguments__ + +* __url__ *{String}* +* __type__ *{String}* + + The data content (MIME) type. + + + +> ```DataMan.URL = function DataManURL(url, type) { ...``` [server/data-man-url.js:10](server/data-man-url.js#L10) + + +- + +### *datamanUrl*.getBuffer(callback)  Server ### + +*This method is private* +*This method __getBuffer__ is defined in `prototype` of `DataMan.URL`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, buffer) + + +__Returns__ *{Buffer|undefined}* + + +Passes a Buffer representing the data at the URL to a callback. + +> ```DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) { ...``` [server/data-man-url.js:24](server/data-man-url.js#L24) + + +- + +### *datamanUrl*.getDataUri(callback)  Server ### + +*This method is private* +*This method __getDataUri__ is defined in `prototype` of `DataMan.URL`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, dataUri) + + + +Passes a data URI representing the data at the URL to a callback. + +> ```DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) { ...``` [server/data-man-url.js:57](server/data-man-url.js#L57) + + +- + +### *datamanUrl*.createReadStream()  Server ### + +*This method is private* +*This method __createReadStream__ is defined in `prototype` of `DataMan.URL`* + + +Returns a read stream for the data. + +> ```DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() { ...``` [server/data-man-url.js:85](server/data-man-url.js#L85) + + +- + +### *datamanUrl*.size(callback)  Server ### + +*This method is private* +*This method __size__ is defined in `prototype` of `DataMan.URL`* + +__Arguments__ + +* __callback__ *{function}* + + callback(err, size) + + + +Returns the size in bytes of the data at the URL. + +> ```DataMan.URL.prototype.size = function dataManUrlSize(callback) { ...``` [server/data-man-url.js:97](server/data-man-url.js#L97) + + +- + +### *datamanUrl*.type()  Server ### + +*This method is private* +*This method __type__ is defined in `prototype` of `DataMan.URL`* + + +Returns the type of the data. + +> ```DataMan.URL.prototype.type = function dataManUrlType() { ...``` [server/data-man-url.js:121](server/data-man-url.js#L121) + + diff --git a/packages/wekan-cfs-data-man/package.js b/packages/wekan-cfs-data-man/package.js new file mode 100644 index 000000000..f0c9062fc --- /dev/null +++ b/packages/wekan-cfs-data-man/package.js @@ -0,0 +1,48 @@ +Package.describe({ + name: 'wekan-cfs-data-man', + version: '0.0.6', + summary: 'A data manager, allowing you to attach various types of data and get it back in various other types', + git: 'https://github.com/zcfs/Meteor-data-man.git' +}); + +Npm.depends({ + mime: "1.2.11", + 'buffer-stream-reader': "0.1.1", + //request: "2.44.0", + // We use a specific commit from a fork of "request" package for now; we need fix for + // https://github.com/mikeal/request/issues/887 (https://github.com/zcfs/Meteor-CollectionFS/issues/347) + request: "https://github.com/wekan/request", + temp: "0.7.0" // for tests only +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use(['ejson']); + + api.use(['wekan-cfs-filesaver@0.0.6'], {weak: true}); + + api.export('DataMan'); + + api.addFiles([ + 'client/Blob.js', //polyfill for browsers without Blob constructor; currently necessary for phantomjs support, too + 'client/data-man-api.js' + ], 'client'); + + api.addFiles([ + 'server/data-man-api.js', + 'server/data-man-buffer.js', + 'server/data-man-datauri.js', + 'server/data-man-filepath.js', + 'server/data-man-url.js', + 'server/data-man-readstream.js' + ], 'server'); + +}); + +Package.onTest(function (api) { + api.use(['wekan-cfs-data-man', 'http', 'tinytest', 'test-helpers', 'wekan-cfs-http-methods@0.0.29']); + + api.addFiles(['tests/common.js', 'tests/client-tests.js'], 'client'); + api.addFiles(['tests/common.js', 'tests/server-tests.js'], 'server'); +}); diff --git a/packages/wekan-cfs-data-man/server/data-man-api.js b/packages/wekan-cfs-data-man/server/data-man-api.js new file mode 100644 index 000000000..58bd2bdf0 --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-api.js @@ -0,0 +1,176 @@ +/* global DataMan:true, Buffer */ + +var fs = Npm.require("fs"); +var Readable = Npm.require('stream').Readable; + +/** + * @method DataMan + * @public + * @constructor + * @param {Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate. + * @param {String} [type] The data content (MIME) type, if known. Required if the first argument is a Buffer, ArrayBuffer, Uint8Array, or URL + * @param {Object} [options] Currently used only to pass options for the GET request when `data` is a URL. + */ +DataMan = function DataMan(data, type, options) { + var self = this, buffer; + + if (!data) { + throw new Error("DataMan constructor requires a data argument"); + } + + // The end result of all this is that we will have this.source set to a correct + // data type handler. We are simply detecting what the data arg is. + // + // Unless we already have in-memory data, we don't load anything into memory + // and instead rely on obtaining a read stream when the time comes. + if (typeof Buffer !== "undefined" && data instanceof Buffer) { + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed a Buffer"); + } + self.source = new DataMan.Buffer(data, type); + } else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) { + if (typeof Buffer === "undefined") { + throw new Error("Buffer support required to handle an ArrayBuffer"); + } + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer"); + } + buffer = new Buffer(new Uint8Array(data)); + self.source = new DataMan.Buffer(buffer, type); + } else if (EJSON.isBinary(data)) { + if (typeof Buffer === "undefined") { + throw new Error("Buffer support required to handle an ArrayBuffer"); + } + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed a Uint8Array"); + } + buffer = new Buffer(data); + self.source = new DataMan.Buffer(buffer, type); + } else if (typeof Readable !== "undefined" && data instanceof Readable) { + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed a stream.Readable"); + } + self.source = new DataMan.ReadStream(data, type); + } else if (typeof data === "string") { + if (data.slice(0, 5) === "data:") { + self.source = new DataMan.DataURI(data); + } else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") { + if (!type) { + throw new Error("DataMan constructor requires a type argument when passed a URL"); + } + self.source = new DataMan.URL(data, type, options); + } else { + // assume it's a filepath + self.source = new DataMan.FilePath(data, type); + } + } else { + throw new Error("DataMan constructor received data that it doesn't support"); + } +}; + +/** + * @method DataMan.prototype.getBuffer + * @public + * @param {function} [callback] callback(err, buffer) + * @returns {Buffer|undefined} + * + * Returns a Buffer representing this data, or passes the Buffer to a callback. + */ +DataMan.prototype.getBuffer = function dataManGetBuffer(callback) { + var self = this; + return callback ? self.source.getBuffer(callback) : Meteor.wrapAsync(bind(self.source.getBuffer, self.source))(); +}; + +function _saveToFile(readStream, filePath, callback) { + var writeStream = fs.createWriteStream(filePath); + writeStream.on('close', Meteor.bindEnvironment(function () { + callback(); + }, function (error) { callback(error); })); + writeStream.on('error', Meteor.bindEnvironment(function (error) { + callback(error); + }, function (error) { callback(error); })); + readStream.pipe(writeStream); +} + +/** + * @method DataMan.prototype.saveToFile + * @public + * @param {String} filePath + * @param {Function} callback + * @returns {undefined} + * + * Saves this data to a filepath on the local filesystem. + */ +DataMan.prototype.saveToFile = function dataManSaveToFile(filePath, callback) { + var readStream = this.createReadStream(); + return callback ? _saveToFile(readStream, filePath, callback) : Meteor.wrapAsync(_saveToFile)(readStream, filePath); +}; + +/** + * @method DataMan.prototype.getDataUri + * @public + * @param {function} [callback] callback(err, dataUri) + * + * If no callback, returns the data URI. + */ +DataMan.prototype.getDataUri = function dataManGetDataUri(callback) { + var self = this; + return callback ? self.source.getDataUri(callback) : Meteor.wrapAsync(bind(self.source.getDataUri, self.source))(); +}; + +/** + * @method DataMan.prototype.createReadStream + * @public + * + * Returns a read stream for the data. + */ +DataMan.prototype.createReadStream = function dataManCreateReadStream() { + return this.source.createReadStream(); +}; + +/** + * @method DataMan.prototype.size + * @public + * @param {function} [callback] callback(err, size) + * + * If no callback, returns the size in bytes of the data. + */ +DataMan.prototype.size = function dataManSize(callback) { + var self = this; + return callback ? self.source.size(callback) : Meteor.wrapAsync(bind(self.source.size, self.source))(); +}; + +/** + * @method DataMan.prototype.type + * @public + * + * Returns the type of the data. + */ +DataMan.prototype.type = function dataManType() { + return this.source.type(); +}; + +/* + * "bind" shim; from underscorejs, but we avoid a dependency + */ +var slice = Array.prototype.slice; +var nativeBind = Function.prototype.bind; +var ctor = function(){}; +function isFunction(obj) { + return Object.prototype.toString.call(obj) == '[object Function]'; +} +function bind(func, context) { + var args, bound; + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!isFunction(func)) throw new TypeError; + args = slice.call(arguments, 2); + return bound = function() { + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); + ctor.prototype = func.prototype; + var self = new ctor; + ctor.prototype = null; + var result = func.apply(self, args.concat(slice.call(arguments))); + if (Object(result) === result) return result; + return self; + }; +} diff --git a/packages/wekan-cfs-data-man/server/data-man-buffer.js b/packages/wekan-cfs-data-man/server/data-man-buffer.js new file mode 100644 index 000000000..2b4c47f25 --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-buffer.js @@ -0,0 +1,82 @@ +var bufferStreamReader = Npm.require('buffer-stream-reader'); + +/** + * @method DataMan.Buffer + * @public + * @constructor + * @param {Buffer} buffer + * @param {String} type The data content (MIME) type. + */ +DataMan.Buffer = function DataManBuffer(buffer, type) { + var self = this; + self.buffer = buffer; + self._type = type; +}; + +/** + * @method DataMan.Buffer.prototype.getBuffer + * @private + * @param {function} callback callback(err, buffer) + * @returns {Buffer|undefined} + * + * Passes a Buffer representing the data to a callback. + */ +DataMan.Buffer.prototype.getBuffer = function dataManBufferGetBuffer(callback) { + callback(null, this.buffer); +}; + +/** + * @method DataMan.Buffer.prototype.getDataUri + * @private + * @param {function} callback callback(err, dataUri) + * + * Passes a data URI representing the data in the buffer to a callback. + */ +DataMan.Buffer.prototype.getDataUri = function dataManBufferGetDataUri(callback) { + var self = this; + if (!self._type) { + callback(new Error("DataMan.getDataUri couldn't get a contentType")); + } else { + var dataUri = "data:" + self._type + ";base64," + self.buffer.toString("base64"); + callback(null, dataUri); + } +}; + +/** + * @method DataMan.Buffer.prototype.createReadStream + * @private + * + * Returns a read stream for the data. + */ +DataMan.Buffer.prototype.createReadStream = function dataManBufferCreateReadStream() { + return new bufferStreamReader(this.buffer); +}; + +/** + * @method DataMan.Buffer.prototype.size + * @param {function} callback callback(err, size) + * @private + * + * Passes the size in bytes of the data in the buffer to a callback. + */ +DataMan.Buffer.prototype.size = function dataManBufferSize(callback) { + var self = this; + + if (typeof self._size === "number") { + callback(null, self._size); + return; + } + + self._size = self.buffer.length; + callback(null, self._size); +}; + +/** + * @method DataMan.Buffer.prototype.type + * @private + * + * Returns the type of the data. + */ +DataMan.Buffer.prototype.type = function dataManBufferType() { + return this._type; +}; diff --git a/packages/wekan-cfs-data-man/server/data-man-datauri.js b/packages/wekan-cfs-data-man/server/data-man-datauri.js new file mode 100644 index 000000000..59e241efb --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-datauri.js @@ -0,0 +1,14 @@ +/** + * @method DataMan.DataURI + * @public + * @constructor + * @param {String} dataUri + */ +DataMan.DataURI = function DataManDataURI(dataUri) { + var self = this; + var pieces = dataUri.match(/^data:(.*);base64,(.*)$/); + var buffer = new Buffer(pieces[2], 'base64'); + return new DataMan.Buffer(buffer, pieces[1]); +}; + +DataMan.DataURI.prototype = DataMan.Buffer.prototype; diff --git a/packages/wekan-cfs-data-man/server/data-man-filepath.js b/packages/wekan-cfs-data-man/server/data-man-filepath.js new file mode 100644 index 000000000..2f3131708 --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-filepath.js @@ -0,0 +1,108 @@ +var mime = Npm.require('mime'); +var fs = Npm.require("fs"); + +/** + * @method DataMan.FilePath + * @public + * @constructor + * @param {String} filepath + * @param {String} [type] The data content (MIME) type. Will lookup from file if not passed. + */ +DataMan.FilePath = function DataManFilePath(filepath, type) { + var self = this; + self.filepath = filepath; + self._type = type || mime.lookup(filepath); +}; + +/** + * @method DataMan.FilePath.prototype.getBuffer + * @private + * @param {function} callback callback(err, buffer) + * @returns {Buffer|undefined} + * + * Passes a Buffer representing the data to a callback. + */ +DataMan.FilePath.prototype.getBuffer = function dataManFilePathGetBuffer(callback) { + var self = this; + + // Call node readFile + fs.readFile(self.filepath, Meteor.bindEnvironment(function(err, buffer) { + callback(err, buffer); + }, function(err) { + callback(err); + })); +}; + +/** + * @method DataMan.FilePath.prototype.getDataUri + * @private + * @param {function} callback callback(err, dataUri) + * + * Passes a data URI representing the data to a callback. + */ +DataMan.FilePath.prototype.getDataUri = function dataManFilePathGetDataUri(callback) { + var self = this; + + self.getBuffer(function (error, buffer) { + if (error) { + callback(error); + } else { + if (!self._type) { + callback(new Error("DataMan.getDataUri couldn't get a contentType")); + } else { + var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64"); + buffer = null; + callback(null, dataUri); + } + } + }); +}; + +/** + * @method DataMan.FilePath.prototype.createReadStream + * @private + * + * Returns a read stream for the data. + */ +DataMan.FilePath.prototype.createReadStream = function dataManFilePathCreateReadStream() { + // Stream from filesystem + return fs.createReadStream(this.filepath); +}; + +/** + * @method DataMan.FilePath.prototype.size + * @param {function} callback callback(err, size) + * @private + * + * Passes the size in bytes of the data to a callback. + */ +DataMan.FilePath.prototype.size = function dataManFilePathSize(callback) { + var self = this; + + if (typeof self._size === "number") { + callback(null, self._size); + return; + } + + // We can get the size without buffering + fs.stat(self.filepath, Meteor.bindEnvironment(function (error, stats) { + if (stats && typeof stats.size === "number") { + self._size = stats.size; + callback(null, self._size); + } else { + callback(error); + } + }, function (error) { + callback(error); + })); +}; + +/** + * @method DataMan.FilePath.prototype.type + * @private + * + * Returns the type of the data. + */ +DataMan.FilePath.prototype.type = function dataManFilePathType() { + return this._type; +}; diff --git a/packages/wekan-cfs-data-man/server/data-man-readstream.js b/packages/wekan-cfs-data-man/server/data-man-readstream.js new file mode 100644 index 000000000..fbfb20391 --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-readstream.js @@ -0,0 +1,80 @@ +/* global DataMan */ + +var PassThrough = Npm.require('stream').PassThrough; + +/** + * @method DataMan.ReadStream + * @public + * @constructor + * @param {ReadStream} stream + * @param {String} type The data content (MIME) type. + */ +DataMan.ReadStream = function DataManBuffer(stream, type) { + var self = this; + + // Create a bufferable / paused new stream... + var pt = new PassThrough(); + + // Pipe provided read stream into pass-through stream + stream.pipe(pt); + + // Set pass-through stream reference + self.stream = pt; + + // Set type as provided + self._type = type; +}; + +/** + * @method DataMan.ReadStream.prototype.getBuffer + * @private + * @param {function} callback callback(err, buffer) + * @returns {undefined} + * + * Passes a Buffer representing the data to a callback. + */ +DataMan.ReadStream.prototype.getBuffer = function dataManReadStreamGetBuffer(/*callback*/) { + // TODO implement as passthrough stream? +}; + +/** + * @method DataMan.ReadStream.prototype.getDataUri + * @private + * @param {function} callback callback(err, dataUri) + * + * Passes a data URI representing the data in the stream to a callback. + */ +DataMan.ReadStream.prototype.getDataUri = function dataManReadStreamGetDataUri(/*callback*/) { + // TODO implement as passthrough stream? +}; + +/** + * @method DataMan.ReadStream.prototype.createReadStream + * @private + * + * Returns a read stream for the data. + */ +DataMan.ReadStream.prototype.createReadStream = function dataManReadStreamCreateReadStream() { + return this.stream; +}; + +/** + * @method DataMan.ReadStream.prototype.size + * @param {function} callback callback(err, size) + * @private + * + * Passes the size in bytes of the data in the stream to a callback. + */ +DataMan.ReadStream.prototype.size = function dataManReadStreamSize(callback) { + callback(0); // will determine from stream later +}; + +/** + * @method DataMan.ReadStream.prototype.type + * @private + * + * Returns the type of the data. + */ +DataMan.ReadStream.prototype.type = function dataManReadStreamType() { + return this._type; +}; diff --git a/packages/wekan-cfs-data-man/server/data-man-url.js b/packages/wekan-cfs-data-man/server/data-man-url.js new file mode 100644 index 000000000..08e61f46f --- /dev/null +++ b/packages/wekan-cfs-data-man/server/data-man-url.js @@ -0,0 +1,133 @@ +var request = Npm.require("request"); + +/** + * @method DataMan.URL + * @public + * @constructor + * @param {String} url + * @param {String} type The data content (MIME) type. + */ +DataMan.URL = function DataManURL(url, type, options) { + var self = this; + options = options || {}; + + self.url = url; + self._type = type; + + // This is some code borrowed from the http package. Hopefully + // we can eventually use HTTP pkg directly instead of 'request' + // once it supports streams and buffers and such. (`request` takes + // and `auth` option, too, but not of the same form as `HTTP`.) + if (options.auth) { + if (options.auth.indexOf(':') < 0) + throw new Error('auth option should be of the form "username:password"'); + options.headers = options.headers || {}; + options.headers['Authorization'] = "Basic "+ + (new Buffer(options.auth, "ascii")).toString("base64"); + delete options.auth; + } + + self.urlOpts = options; +}; + +/** + * @method DataMan.URL.prototype.getBuffer + * @private + * @param {function} callback callback(err, buffer) + * @returns {Buffer|undefined} + * + * Passes a Buffer representing the data at the URL to a callback. + */ +DataMan.URL.prototype.getBuffer = function dataManUrlGetBuffer(callback) { + var self = this; + + request(_.extend({ + url: self.url, + method: "GET", + encoding: null, + jar: false + }, self.urlOpts), Meteor.bindEnvironment(function(err, res, body) { + if (err) { + callback(err); + } else { + self._type = res.headers['content-type']; + callback(null, body); + } + }, function(err) { + callback(err); + })); +}; + +/** + * @method DataMan.URL.prototype.getDataUri + * @private + * @param {function} callback callback(err, dataUri) + * + * Passes a data URI representing the data at the URL to a callback. + */ +DataMan.URL.prototype.getDataUri = function dataManUrlGetDataUri(callback) { + var self = this; + + self.getBuffer(function (error, buffer) { + if (error) { + callback(error); + } else { + if (!self._type) { + callback(new Error("DataMan.getDataUri couldn't get a contentType")); + } else { + var dataUri = "data:" + self._type + ";base64," + buffer.toString("base64"); + callback(null, dataUri); + } + } + }); +}; + +/** + * @method DataMan.URL.prototype.createReadStream + * @private + * + * Returns a read stream for the data. + */ +DataMan.URL.prototype.createReadStream = function dataManUrlCreateReadStream() { + var self = this; + // Stream from URL + return request(_.extend({ + url: self.url, + method: "GET" + }, self.urlOpts)); +}; + +/** + * @method DataMan.URL.prototype.size + * @param {function} callback callback(err, size) + * @private + * + * Returns the size in bytes of the data at the URL. + */ +DataMan.URL.prototype.size = function dataManUrlSize(callback) { + var self = this; + + if (typeof self._size === "number") { + callback(null, self._size); + return; + } + + self.getBuffer(function (error, buffer) { + if (error) { + callback(error); + } else { + self._size = buffer.length; + callback(null, self._size); + } + }); +}; + +/** + * @method DataMan.URL.prototype.type + * @private + * + * Returns the type of the data. + */ +DataMan.URL.prototype.type = function dataManUrlType() { + return this._type; +}; diff --git a/packages/wekan-cfs-data-man/tests/client-tests.js b/packages/wekan-cfs-data-man/tests/client-tests.js new file mode 100644 index 000000000..60e64d3ba --- /dev/null +++ b/packages/wekan-cfs-data-man/tests/client-tests.js @@ -0,0 +1,296 @@ +var blobData; +var arrayBufferData; +var binaryData; +var dataUriData; +var urlData; + +// Init with Blob +Tinytest.addAsync('cfs-data - client - Init with Blob', function(test, onComplete) { + var blob = new Blob(['Hello World'], {type : 'text/plain'}); + blobData = new DataMan(blob); + test.instanceOf(blobData.blob, Blob); + test.equal(blobData.type(), "text/plain"); + onComplete(); +}); + +// Init with ArrayBuffer +Tinytest.addAsync('cfs-data - client - Init with ArrayBuffer', function(test, onComplete) { + arrayBufferData = new DataMan(str2ab('Hello World'), "text/plain"); + // Should be converted upon init to a Blob + test.instanceOf(arrayBufferData.blob, Blob); + test.equal(arrayBufferData.type(), "text/plain"); + onComplete(); +}); + +// Init with Binary +Tinytest.addAsync('cfs-data - client - Init with Binary', function(test, onComplete) { + binaryData = new DataMan(new Uint8Array(str2ab('Hello World')), "text/plain"); + // Should be converted upon init to a Blob + test.instanceOf(arrayBufferData.blob, Blob); + test.equal(binaryData.type(), "text/plain"); + onComplete(); +}); + +// Init with data URI string +Tinytest.addAsync('cfs-data - client - Init with data URI string', function(test, onComplete) { + var dataUri = 'data:text/plain;base64,SGVsbG8gV29ybGQ='; //'Hello World' + dataUriData = new DataMan(dataUri); + // Should be converted upon init to a Blob + test.instanceOf(dataUriData.blob, Blob); + test.equal(dataUriData.type(), "text/plain"); //should be extracted from data URI + onComplete(); +}); + +// Init with URL string +Tinytest.addAsync('cfs-data - client - Init with URL string', function(test, onComplete) { + urlData = new DataMan(Meteor.absoluteUrl('test'), "text/plain"); //'Hello World' + // URLs are not converted to Blobs upon init + test.equal(urlData.url, Meteor.absoluteUrl('test')); + test.equal(urlData.type(), "text/plain"); + onComplete(); +}); + +// getBlob +Tinytest.addAsync('cfs-data - client - getBlob', function(test, onComplete) { + var total = 10, done = 0; + function continueIfDone() { + done++; + if (total === done) { + onComplete(); + } + } + + function testBlob(error, blob, testType) { + test.isFalse(!!error, testType + ' got error: ' + (error && error.message)); + test.instanceOf(blob, Blob, testType + ' got no blob'); + + if (blob instanceof Blob) { + var reader = new FileReader(); + reader.addEventListener("load", function(event) { + test.equal(reader.result, 'Hello World', testType + ' got back blob with incorrect data'); + continueIfDone(); + }, false); + reader.addEventListener("error", function(err) { + test.equal(reader.error, null, testType + ' error reading blob as text'); + continueIfDone(); + }, false); + reader.readAsText(blob, 'utf-8'); + } else { + continueIfDone(); + } + } + + // from Blob + blobData.getBlob(function (error, blob) { + testBlob(error, blob, 'getBlob from Blob'); + }); + + // from Blob (no callback) + testBlob(false, blobData.getBlob(), 'getBlob from Blob'); + + // from ArrayBuffer + arrayBufferData.getBlob(function (error, blob) { + testBlob(error, blob, 'getBlob from ArrayBuffer'); + }); + + // from ArrayBuffer (no callback) + testBlob(false, arrayBufferData.getBlob(), 'getBlob from ArrayBuffer'); + + // from binary + binaryData.getBlob(function (error, blob) { + testBlob(error, blob, 'getBlob from binary'); + }); + + // from binary (no callback) + testBlob(false, binaryData.getBlob(), 'getBlob from binary'); + + // from data URI + dataUriData.getBlob(function (error, blob) { + testBlob(error, blob, 'getBlob from data URI'); + }); + + // from data URI (no callback) + testBlob(false, dataUriData.getBlob(), 'getBlob from data URI'); + + // from URL + urlData.getBlob(function (error, blob) { + testBlob(error, blob, 'getBlob from URL'); + }); + + // from URL (no callback) + test.throws(function () { + // callback is required for URLs on the client + urlData.getBlob(); + }); + continueIfDone(); + +}); + +// getBinary +Tinytest.addAsync('cfs-data - client - getBinary', function(test, onComplete) { + var total = 5, done = 0; + function continueIfDone() { + done++; + if (total === done) { + onComplete(); + } + } + + function testBinary(error, binary, testType) { + test.isFalse(!!error, testType + ' got error: ' + (error && error.message)); + test.isTrue(EJSON.isBinary(binary), testType + ' got no binary'); + + if (EJSON.isBinary(binary)) { + test.equal(bin2str(binary), 'Hello World', testType + ' got back binary with incorrect data'); + continueIfDone(); + } else { + continueIfDone(); + } + } + + // from Blob + blobData.getBinary(function (error, binary) { + testBinary(error, binary, 'getBinary from Blob'); + }); + + // from ArrayBuffer + arrayBufferData.getBinary(function (error, binary) { + testBinary(error, binary, 'getBinary from ArrayBuffer'); + }); + + // from binary + binaryData.getBinary(function (error, binary) { + testBinary(error, binary, 'getBinary from binary'); + }); + + // from data URI + dataUriData.getBinary(function (error, binary) { + testBinary(error, binary, 'getBinary from data URI'); + }); + + // from URL + urlData.getBinary(function (error, binary) { + testBinary(error, binary, 'getBinary from URL'); + }); +}); + +// getDataUri +Tinytest.addAsync('cfs-data - client - getDataUri', function(test, onComplete) { + var total = 5, done = 0; + function testURI(error, uri, testType) { + test.isFalse(!!error, testType + ' got error: ' + (error && error.message)); + test.equal(typeof uri, "string", testType + ' got no URI string'); + test.equal(uri, 'data:text/plain;base64,SGVsbG8gV29ybGQ=', testType + ' got invalid URI'); + + done++; + if (total === done) { + onComplete(); + } + } + + // from Blob + blobData.getDataUri(function (error, uri) { + testURI(error, uri, 'getDataUri from Blob'); + }); + + // from ArrayBuffer + arrayBufferData.getDataUri(function (error, uri) { + testURI(error, uri, 'getDataUri from ArrayBuffer'); + }); + + // from binary + binaryData.getDataUri(function (error, uri) { + testURI(error, uri, 'getDataUri from binary'); + }); + + // from data URI + dataUriData.getDataUri(function (error, uri) { + testURI(error, uri, 'getDataUri from data URI'); + }); + + // from URL + urlData.getDataUri(function (error, uri) { + testURI(error, uri, 'getDataUri from URL'); + }); +}); + +// size +Tinytest.addAsync('cfs-data - client - size', function(test, onComplete) { + var total = 10, done = 0; + function continueIfDone() { + done++; + if (total === done) { + onComplete(); + } + } + + function testSize(error, size, testType) { + test.isFalse(!!error, testType + ' got error: ' + (error && error.message)); + test.equal(size, 11, testType + ' got wrong size'); + continueIfDone(); + } + + // from Blob + blobData.size(function (error, size) { + testSize(error, size, 'size from Blob'); + }); + + // from Blob (no callback) + testSize(false, blobData.size(), 'size from Blob'); + + // from ArrayBuffer + arrayBufferData.size(function (error, size) { + testSize(error, size, 'size from ArrayBuffer'); + }); + + // from ArrayBuffer (no callback) + testSize(false, arrayBufferData.size(), 'size from ArrayBuffer'); + + // from binary + binaryData.size(function (error, size) { + testSize(error, size, 'size from binary'); + }); + + // from binary (no callback) + testSize(false, binaryData.size(), 'size from binary'); + + // from data URI + dataUriData.size(function (error, size) { + testSize(error, size, 'size from data URI'); + }); + + // from data URI (no callback) + testSize(false, dataUriData.size(), 'size from data URI'); + + // from URL + urlData.size(function (error, size) { + testSize(error, size, 'size from URL'); + }); + + // from URL (no callback) + test.throws(function () { + // callback is required for URLs on the client + urlData.size(); + }); + continueIfDone(); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-data-man/tests/common.js b/packages/wekan-cfs-data-man/tests/common.js new file mode 100644 index 000000000..34eeabc34 --- /dev/null +++ b/packages/wekan-cfs-data-man/tests/common.js @@ -0,0 +1,38 @@ +// ab2str = function ab2str(buf) { +// return String.fromCharCode(new Uint8Array(buf)); +// } + +bin2str = function bin2str(bufView) { + var length = bufView.length; + var result = ''; + for (var i = 0; i length) { + addition = length - i; + } + try { + // this fails on phantomjs due to old webkit bug; hence the try/catch + result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition)); + } catch (e) { + var dataArray = []; + for (var j = i; j < i+addition; j++) { + dataArray.push(bufView[j]); + } + result += String.fromCharCode.apply(null, dataArray); + } + } + return result; +}; + +ab2str = function ab2str(buffer) { + return bin2str(new Uint8Array(buffer)); +}; + +str2ab = function str2ab(str) { + var buf = new ArrayBuffer(str.length); + var bufView = new Uint8Array(buf); + for (var i=0, strLen=str.length; inew *fs*.File([ref])  Anywhere ### + +*This method __File__ is defined in `FS`* + +__Arguments__ + +* __ref__ *{object|[FS.File](#FS.File)|[data to attach](#data to attach)}* (Optional) + + Another FS.File instance, a filerecord, or some data to pass to attachData + + + +> ```FS.File = function(ref, createdByTransform) { ...``` [fsFile-common.js:8](fsFile-common.js#L8) + + +- + +### *fsFile*.attachData(data, [options], [callback])  Anywhere ### + +*This method __attachData__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __data__ *{[File](#File)|[Blob](#Blob)|Buffer|ArrayBuffer|Uint8Array|String}* + + The data that you want to attach to the file. + +* __options__ *{Object}* (Optional) + + Options + + * __type__ *{String}* (Optional) + + The data content (MIME) type, if known. + + * __headers__ *{String}* (Optional) + + When attaching a URL, headers to be used for the GET request (currently server only) + + * __auth__ *{String}* (Optional) + + When attaching a URL, "username:password" to be used for the GET request (currently server only) + +* __callback__ *{Function}* (Optional) + + Callback function, callback(error). On the client, a callback is required if data is a URL. + + +__Returns__ *{FS.File}* +This FS.File instance. + + + +> ```FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) { ...``` [fsFile-common.js:36](fsFile-common.js#L36) + + +- + +### *fsFile*.uploadProgress()  Anywhere ### + +*This method __uploadProgress__ is defined in `prototype` of `FS.File`* + +__Returns__ *{number}* +The server confirmed upload progress + + +> ```FS.File.prototype.uploadProgress = function() { ...``` [fsFile-common.js:154](fsFile-common.js#L154) + + +- + +### *fsFile*.controlledByDeps()  Anywhere ### + +*This method __controlledByDeps__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns true if this FS.File is reactive + + +> Note: Returns true if this FS.File object was created by a FS.Collection +> and we are in a reactive computations. What does this mean? Well it should +> mean that our fileRecord is fully updated by Meteor and we are mounted on +> a collection + +> ```FS.File.prototype.controlledByDeps = function() { ...``` [fsFile-common.js:179](fsFile-common.js#L179) + + +- + +### *fsFile*.getCollection()  Anywhere ### + +*This method __getCollection__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns attached collection or undefined if not mounted + + +> ```FS.File.prototype.getCollection = function() { ...``` [fsFile-common.js:189](fsFile-common.js#L189) + + +- + +### *fsFile*.isMounted()  Anywhere ### + +*This method __isMounted__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns attached collection or undefined if not mounted + + +> ```FS.File.prototype.isMounted = FS.File.prototype.getCollection;``` [fsFile-common.js:217](fsFile-common.js#L217) + + +- + +### *fsFile*.getFileRecord()  Anywhere ### + +*This method __getFileRecord__ is defined in `prototype` of `FS.File`* + +__Returns__ *{object}* +The filerecord + + +> ```FS.File.prototype.getFileRecord = function() { ...``` [fsFile-common.js:224](fsFile-common.js#L224) + + +- + +### *fsFile*.update(modifier, [options], [callback])  Anywhere ### + +*This method __update__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __modifier__ *{[modifier](#modifier)}* +* __options__ *{object}* (Optional) +* __callback__ *{function}* (Optional) + + +Updates the fileRecord. + +> ```FS.File.prototype.update = function(modifier, options, callback) { ...``` [fsFile-common.js:255](fsFile-common.js#L255) + + +- + +### *fsFile*.remove([callback])  Anywhere ### + +*This method __remove__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __callback__ *{Function}* (Optional) + +__Returns__ *{number}* +Count + + +Remove the current file from its FS.Collection + +> ```FS.File.prototype.remove = function(callback) { ...``` [fsFile-common.js:323](fsFile-common.js#L323) + + +- + +### *fsFile*.getExtension([options])  Anywhere ### + +> __Warning!__ +> This method "FS.File.prototype.getExtension" has deprecated from the API +> Use the `extension` getter/setter method instead. + +*This method __getExtension__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{Object}* (Optional) + * __store__ *{String}* (Optional) + + Store name. Default is the original extension. + + +__Returns__ *{string}* +The extension eg.: `jpg` or if not found then an empty string '' + + +> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364) + + +- + +### *fsFile*.isImage([options])  Anywhere ### + +*This method __isImage__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has an image +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isImage = function(options) { ...``` [fsFile-common.js:393](fsFile-common.js#L393) + + +- + +### *fsFile*.isVideo([options])  Anywhere ### + +*This method __isVideo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has a video +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isVideo = function(options) { ...``` [fsFile-common.js:408](fsFile-common.js#L408) + + +- + +### *fsFile*.isAudio([options])  Anywhere ### + +*This method __isAudio__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has an audio +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isAudio = function(options) { ...``` [fsFile-common.js:423](fsFile-common.js#L423) + + +- + +### *fsFile*.formattedSize({Object}, {String}, {String})  Anywhere ### + +*This method __formattedSize__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __{Object}__ *{any}* + + options + +* __{String}__ *{any}* + + [options.store=none,display original file size] Which file do you want to get the size of? + +* __{String}__ *{any}* + + [options.formatString='0.00 b'] The `numeral` format string to use. + + +__Returns__ *{String}* +The file size formatted as a human readable string and reactively updated. + + +You must add the `numeral` package to your app before you can use this method. +If info is not found or a size can't be determined, it will show 0. + +> ```FS.File.prototype.formattedSize = function fsFileFormattedSize(options) { ...``` [fsFile-common.js:438](fsFile-common.js#L438) + + +- + +### *fsFile*.isUploaded()  Anywhere ### + +*This method __isUploaded__ is defined in `prototype` of `FS.File`* + +__Returns__ *{boolean}* +True if the number of uploaded bytes is equal to the file size. + + +> ```FS.File.prototype.isUploaded = function() { ...``` [fsFile-common.js:456](fsFile-common.js#L456) + + +- + +### *fsFile*.hasStored(storeName, [optimistic])  Anywhere ### + +*This method __hasStored__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{string}* + + Name of the store + +* __optimistic__ *{boolean}* (Optional, Default = false) + + In case that the file record is not found, read below + + +__Returns__ *{boolean}* +Is a version of this file stored in the given store? + + +> Note: If the file is not published to the client or simply not found: +this method cannot know for sure if it exists or not. The `optimistic` +param is the boolean value to return. Are we `optimistic` that the copy +could exist. This is the case in `FS.File.url` we are optimistic that the +copy supplied by the user exists. + +> ```FS.File.prototype.hasStored = function(storeName, optimistic) { ...``` [fsFile-common.js:478](fsFile-common.js#L478) + + +- + +### *fsFile*.getCopyInfo(storeName)  Anywhere ### + +> __Warning!__ +> This method "FS.File.prototype.getCopyInfo" has deprecated from the API +> Use individual methods with `store` option instead. + +*This method __getCopyInfo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{string}* + + Name of the store for which to get copy info. + + +__Returns__ *{Object}* +The file details, e.g., name, size, key, etc., specific to the copy saved in this store. + + +> ```FS.File.prototype.getCopyInfo = function(storeName) { ...``` [fsFile-common.js:504](fsFile-common.js#L504) + + +- + +### *fsFile*.name([value], [options])  Anywhere ### + +*This method __name__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String|null}* (Optional) + + If setting the name, specify the new name as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the name of the version of the file that was saved in this store. Default is the original file name. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file name. + + +> ```FS.File.prototype.name = function(value, options) { ...``` [fsFile-common.js:568](fsFile-common.js#L568) + + +- + +### *fsFile*.extension([value], [options])  Anywhere ### + +*This method __extension__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String|null}* (Optional) + + If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the extension of the version of the file that was saved in this store. Default is the original file extension. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one. + + +> ```FS.File.prototype.extension = function(value, options) { ...``` [fsFile-common.js:593](fsFile-common.js#L593) + + +- + +### *fsFile*.size([value], [options])  Anywhere ### + +*This method __size__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{Number}* (Optional) + + If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the size of the version of the file that was saved in this store. Default is the original file size. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{Number|undefined}* +If setting, returns `undefined`. If getting, returns the file size. + + +> ```FS.File.prototype.size = function(value, options) { ...``` [fsFile-common.js:618](fsFile-common.js#L618) + + +- + +### *fsFile*.type([value], [options])  Anywhere ### + +*This method __type__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String}* (Optional) + + If setting the type, specify the new type as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the type of the version of the file that was saved in this store. Default is the original file type. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file type. + + +> ```FS.File.prototype.type = function(value, options) { ...``` [fsFile-common.js:643](fsFile-common.js#L643) + + +- + +### *fsFile*.updatedAt([value], [options])  Anywhere ### + +*This method __updatedAt__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String}* (Optional) + + If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file's last updated date. + + +> ```FS.File.prototype.updatedAt = function(value, options) { ...``` [fsFile-common.js:668](fsFile-common.js#L668) + + +- + +### *fsFile*.createReadStream([storeName])  Server ### + +*This method __createReadStream__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* (Optional) + +__Returns__ *{stream.Readable}* +Readable NodeJS stream + + +Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name. + +If you pass a `storeName`, a readable stream for the file data saved in that store is returned. +If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned. +If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned. + + +> ```FS.File.prototype.createReadStream = function(storeName) { ...``` [fsFile-server.js:62](fsFile-server.js#L62) + + +- + +### *fsFile*.createWriteStream([storeName])  Server ### + +*This method __createWriteStream__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* (Optional) + +__Returns__ *{stream.Writeable}* +Writeable NodeJS stream + + +Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name. + +If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned. +If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned. + + +> ```FS.File.prototype.createWriteStream = function(storeName) { ...``` [fsFile-server.js:100](fsFile-server.js#L100) + + +- + +### *fsFile*.copy()  Server ### + +*This method __copy__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.File}* +The new FS.File instance + + +> ```FS.File.prototype.copy = function() { ...``` [fsFile-server.js:126](fsFile-server.js#L126) + + diff --git a/packages/wekan-cfs-file/fsFile-common.js b/packages/wekan-cfs-file/fsFile-common.js new file mode 100644 index 000000000..01bbffbe4 --- /dev/null +++ b/packages/wekan-cfs-file/fsFile-common.js @@ -0,0 +1,765 @@ +/** + * @method FS.File + * @namespace FS.File + * @public + * @constructor + * @param {object|FS.File|data to attach} [ref] Another FS.File instance, a filerecord, or some data to pass to attachData + */ +FS.File = function(ref, createdByTransform) { + var self = this; + + self.createdByTransform = !!createdByTransform; + + if (ref instanceof FS.File || isBasicObject(ref)) { + // Extend self with filerecord related data + FS.Utility.extend(self, FS.Utility.cloneFileRecord(ref, {full: true})); + } else if (ref) { + self.attachData(ref); + } +}; + +// An FS.File can emit events +FS.File.prototype = new EventEmitter(); + +/** + * @method FS.File.prototype.attachData + * @public + * @param {File|Blob|Buffer|ArrayBuffer|Uint8Array|String} data The data that you want to attach to the file. + * @param {Object} [options] Options + * @param {String} [options.type] The data content (MIME) type, if known. + * @param {String} [options.headers] When attaching a URL, headers to be used for the GET request (currently server only) + * @param {String} [options.auth] When attaching a URL, "username:password" to be used for the GET request (currently server only) + * @param {Function} [callback] Callback function, callback(error). On the client, a callback is required if data is a URL. + * @returns {FS.File} This FS.File instance. + * + */ +FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) { + var self = this; + + if (!callback && typeof options === "function") { + callback = options; + options = {}; + } + options = options || {}; + + if (!data) { + throw new Error('FS.File.attachData requires a data argument with some data'); + } + + var urlOpts; + + // Set any other properties we can determine from the source data + // File + if (typeof File !== "undefined" && data instanceof File) { + self.name(data.name); + self.updatedAt(data.lastModifiedDate); + self.size(data.size); + setData(data.type); + } + // Blob + else if (typeof Blob !== "undefined" && data instanceof Blob) { + self.name(data.name); + self.updatedAt(new Date()); + self.size(data.size); + setData(data.type); + } + // URL: we need to do a HEAD request to get the type because type + // is required for filtering to work. + else if (typeof data === "string" && (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:")) { + urlOpts = FS.Utility.extend({}, options); + if (urlOpts.type) { + delete urlOpts.type; + } + + if (!callback) { + if (Meteor.isClient) { + throw new Error('FS.File.attachData requires a callback when attaching a URL on the client'); + } + var result = Meteor.call('_cfs_getUrlInfo', data, urlOpts); + FS.Utility.extend(self, {original: result}); + setData(result.type); + } else { + Meteor.call('_cfs_getUrlInfo', data, urlOpts, function (error, result) { + FS.debug && console.log("URL HEAD RESULT:", result); + if (error) { + callback(error); + } else { + var type = result.type || options.type; + if (! type) { + throw new Error('FS.File.attachData got a URL for which it could not determine the MIME type and none was provided using options.type'); + } + FS.Utility.extend(self, {original: result}); + setData(type); + } + }); + } + } + // Everything else + else { + setData(options.type); + } + + // Set the data + function setData(type) { + self.data = new DataMan(data, type, urlOpts); + + // Update the type to match what the data is + self.type(self.data.type()); + + // Update the size to match what the data is. + // It's always safe to call self.data.size() without supplying a callback + // because it requires a callback only for URLs on the client, and we + // already added size for URLs when we got the result from '_cfs_getUrlInfo' method. + if (!self.size()) { + if (callback) { + self.data.size(function (error, size) { + if (error) { + callback && callback(error); + } else { + self.size(size); + setName(); + } + }); + } else { + self.size(self.data.size()); + setName(); + } + } else { + setName(); + } + } + + function setName() { + // See if we can extract a file name from URL or filepath + if (!self.name() && typeof data === "string") { + // name from URL + if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") { + if (FS.Utility.getFileExtension(data).length) { + // for a URL we assume the end is a filename only if it has an extension + self.name(FS.Utility.getFileName(data)); + } + } + // name from filepath + else if (data.slice(0, 5) !== "data:") { + self.name(FS.Utility.getFileName(data)); + } + } + + callback && callback(); + } + + return self; //allow chaining +}; + +/** + * @method FS.File.prototype.uploadProgress + * @public + * @returns {number} The server confirmed upload progress + */ +FS.File.prototype.uploadProgress = function() { + var self = this; + // Make sure our file record is updated + self.getFileRecord(); + + // If fully uploaded, return 100 + if (self.uploadedAt) { + return 100; + } + // Otherwise return the confirmed progress or 0 + else { + return Math.round((self.chunkCount || 0) / (self.chunkSum || 1) * 100); + } +}; + +/** + * @method FS.File.prototype.controlledByDeps + * @public + * @returns {FS.Collection} Returns true if this FS.File is reactive + * + * > Note: Returns true if this FS.File object was created by a FS.Collection + * > and we are in a reactive computations. What does this mean? Well it should + * > mean that our fileRecord is fully updated by Meteor and we are mounted on + * > a collection + */ +FS.File.prototype.controlledByDeps = function() { + var self = this; + return self.createdByTransform && Deps.active; +}; + +/** + * @method FS.File.prototype.getCollection + * @public + * @returns {FS.Collection} Returns attached collection or undefined if not mounted + */ +FS.File.prototype.getCollection = function() { + // Get the collection reference + var self = this; + + // If we already made the link then do no more + if (self.collection) { + return self.collection; + } + + // If we don't have a collectionName then there's not much to do, the file is + // not mounted yet + if (!self.collectionName) { + // Should not throw an error here - could be common that the file is not + // yet mounted into a collection + return; + } + + // Link the collection to the file + self.collection = FS._collections[self.collectionName]; + + return self.collection; //possibly undefined, but that's desired behavior +}; + +/** + * @method FS.File.prototype.isMounted + * @public + * @returns {FS.Collection} Returns attached collection or undefined if not mounted + */ +FS.File.prototype.isMounted = FS.File.prototype.getCollection; + +/** + * @method FS.File.prototype.getFileRecord Returns the fileRecord + * @public + * @returns {object} The filerecord + */ +FS.File.prototype.getFileRecord = function() { + var self = this; + // Check if this file object fileRecord is kept updated by Meteor, if so + // return self + if (self.controlledByDeps()) { + return self; + } + // Go for manually updating the file record + if (self.isMounted()) { + FS.debug && console.log('GET FILERECORD: ' + self._id); + + // Return the fileRecord or an empty object + var fileRecord = self.collection.files.findOne({_id: self._id}) || {}; + FS.Utility.extend(self, fileRecord); + return fileRecord; + } else { + // We return an empty object, this way users can still do `getRecord().size` + // Without getting an error + return {}; + } +}; + +/** + * @method FS.File.prototype.update + * @public + * @param {modifier} modifier + * @param {object} [options] + * @param {function} [callback] + * + * Updates the fileRecord. + */ +FS.File.prototype.update = function(modifier, options, callback) { + var self = this; + + FS.debug && console.log('UPDATE: ' + JSON.stringify(modifier)); + + // Make sure we have options and callback + if (!callback && typeof options === 'function') { + callback = options; + options = {}; + } + callback = callback || FS.Utility.defaultCallback; + + if (!self.isMounted()) { + callback(new Error("Cannot update a file that is not associated with a collection")); + return; + } + + // Call collection update - File record + return self.collection.files.update({_id: self._id}, modifier, options, function(err, count) { + // Update the fileRecord if it was changed and on the client + // The server-side methods will pull the fileRecord if needed + if (count > 0 && Meteor.isClient) + self.getFileRecord(); + // Call callback + callback(err, count); + }); +}; + +/** + * @method FS.File.prototype._saveChanges + * @private + * @param {String} [what] "_original" to save original info, or a store name to save info for that store, or saves everything + * + * Updates the fileRecord from values currently set on the FS.File instance. + */ +FS.File.prototype._saveChanges = function(what) { + var self = this; + + if (!self.isMounted()) { + return; + } + + FS.debug && console.log("FS.File._saveChanges:", what || "all"); + + var mod = {$set: {}}; + if (what === "_original") { + mod.$set.original = self.original; + } else if (typeof what === "string") { + var info = self.copies[what]; + if (info) { + mod.$set["copies." + what] = info; + } + } else { + mod.$set.original = self.original; + mod.$set.copies = self.copies; + } + + self.update(mod); +}; + +/** + * @method FS.File.prototype.remove + * @public + * @param {Function} [callback] + * @returns {number} Count + * + * Remove the current file from its FS.Collection + */ +FS.File.prototype.remove = function(callback) { + var self = this; + + FS.debug && console.log('REMOVE: ' + self._id); + + callback = callback || FS.Utility.defaultCallback; + + if (!self.isMounted()) { + callback(new Error("Cannot remove a file that is not associated with a collection")); + return; + } + + return self.collection.files.remove({_id: self._id}, function(err, res) { + if (!err) { + delete self._id; + delete self.collection; + delete self.collectionName; + } + callback(err, res); + }); +}; + +/** + * @method FS.File.prototype.moveTo + * @param {FS.Collection} targetCollection + * @private // Marked private until implemented + * @todo Needs to be implemented + * + * Move the file from current collection to another collection + * + * > Note: Not yet implemented + */ + +/** + * @method FS.File.prototype.getExtension Returns the lowercase file extension + * @public + * @deprecated Use the `extension` getter/setter method instead. + * @param {Object} [options] + * @param {String} [options.store] - Store name. Default is the original extension. + * @returns {string} The extension eg.: `jpg` or if not found then an empty string '' + */ +FS.File.prototype.getExtension = function(options) { + var self = this; + return self.extension(options); +}; + +function checkContentType(fsFile, storeName, startOfType) { + var type; + if (storeName && fsFile.hasStored(storeName)) { + type = fsFile.type({store: storeName}); + } else { + type = fsFile.type(); + } + if (typeof type === "string") { + return type.indexOf(startOfType) === 0; + } + return false; +} + +/** + * @method FS.File.prototype.isImage Is it an image file? + * @public + * @param {object} [options] + * @param {string} [options.store] The store we're interested in + * + * Returns true if the copy of this file in the specified store has an image + * content type. If the file object is unmounted or doesn't have a copy for + * the specified store, or if you don't specify a store, this method checks + * the content type of the original file. + */ +FS.File.prototype.isImage = function(options) { + return checkContentType(this, (options || {}).store, 'image/'); +}; + +/** + * @method FS.File.prototype.isVideo Is it a video file? + * @public + * @param {object} [options] + * @param {string} [options.store] The store we're interested in + * + * Returns true if the copy of this file in the specified store has a video + * content type. If the file object is unmounted or doesn't have a copy for + * the specified store, or if you don't specify a store, this method checks + * the content type of the original file. + */ +FS.File.prototype.isVideo = function(options) { + return checkContentType(this, (options || {}).store, 'video/'); +}; + +/** + * @method FS.File.prototype.isAudio Is it an audio file? + * @public + * @param {object} [options] + * @param {string} [options.store] The store we're interested in + * + * Returns true if the copy of this file in the specified store has an audio + * content type. If the file object is unmounted or doesn't have a copy for + * the specified store, or if you don't specify a store, this method checks + * the content type of the original file. + */ +FS.File.prototype.isAudio = function(options) { + return checkContentType(this, (options || {}).store, 'audio/'); +}; + +/** + * @method FS.File.prototype.formattedSize + * @public + * @param {Object} options + * @param {String} [options.store=none,display original file size] Which file do you want to get the size of? + * @param {String} [options.formatString='0.00 b'] The `numeral` format string to use. + * @return {String} The file size formatted as a human readable string and reactively updated. + * + * * You must add the `numeral` package to your app before you can use this method. + * * If info is not found or a size can't be determined, it will show 0. + */ +FS.File.prototype.formattedSize = function fsFileFormattedSize(options) { + var self = this; + + if (typeof numeral !== "function") + throw new Error("You must add the numeral package if you call FS.File.formattedSize"); + + options = options || {}; + options = options.hash || options; + + var size = self.size(options) || 0; + return numeral(size).format(options.formatString || '0.00 b'); +}; + +/** + * @method FS.File.prototype.isUploaded Is this file completely uploaded? + * @public + * @returns {boolean} True if the number of uploaded bytes is equal to the file size. + */ +FS.File.prototype.isUploaded = function() { + var self = this; + + // Make sure we use the updated file record + self.getFileRecord(); + + return !!self.uploadedAt; +}; + +/** + * @method FS.File.prototype.hasStored + * @public + * @param {string} storeName Name of the store + * @param {boolean} [optimistic=false] In case that the file record is not found, read below + * @returns {boolean} Is a version of this file stored in the given store? + * + * > Note: If the file is not published to the client or simply not found: + * this method cannot know for sure if it exists or not. The `optimistic` + * param is the boolean value to return. Are we `optimistic` that the copy + * could exist. This is the case in `FS.File.url` we are optimistic that the + * copy supplied by the user exists. + */ +FS.File.prototype.hasStored = function(storeName, optimistic) { + var self = this; + // Make sure we use the updated file record + self.getFileRecord(); + // If we havent the published data then + if (FS.Utility.isEmpty(self.copies)) { + return !!optimistic; + } + if (typeof storeName === "string") { + // Return true only if the `key` property is present, which is not set until + // storage is complete. + return !!(self.copies && self.copies[storeName] && self.copies[storeName].key); + } + return false; +}; + +// Backwards compatibility +FS.File.prototype.hasCopy = FS.File.prototype.hasStored; + +/** + * @method FS.File.prototype.getCopyInfo + * @public + * @deprecated Use individual methods with `store` option instead. + * @param {string} storeName Name of the store for which to get copy info. + * @returns {Object} The file details, e.g., name, size, key, etc., specific to the copy saved in this store. + */ +FS.File.prototype.getCopyInfo = function(storeName) { + var self = this; + // Make sure we use the updated file record + self.getFileRecord(); + return (self.copies && self.copies[storeName]) || null; +}; + +/** + * @method FS.File.prototype._getInfo + * @private + * @param {String} [storeName] Name of the store for which to get file info. Omit for original file details. + * @param {Object} [options] + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? + * @returns {Object} The file details, e.g., name, size, key, etc. If not found, returns an empty object. + */ +FS.File.prototype._getInfo = function(storeName, options) { + var self = this; + options = options || {}; + + if (options.updateFileRecordFirst) { + // Make sure we use the updated file record + self.getFileRecord(); + } + + if (storeName) { + return (self.copies && self.copies[storeName]) || {}; + } else { + return self.original || {}; + } +}; + +/** + * @method FS.File.prototype._setInfo + * @private + * @param {String} storeName - Name of the store for which to set file info. Non-string will set original file details. + * @param {String} property - Property to set + * @param {String} value - New value for property + * @param {Boolean} save - Should the new value be saved to the DB, too, or just set in the FS.File properties? + * @returns {undefined} + */ +FS.File.prototype._setInfo = function(storeName, property, value, save) { + var self = this; + if (typeof storeName === "string") { + self.copies = self.copies || {}; + self.copies[storeName] = self.copies[storeName] || {}; + self.copies[storeName][property] = value; + save && self._saveChanges(storeName); + } else { + self.original = self.original || {}; + self.original[property] = value; + save && self._saveChanges("_original"); + } +}; + +/** + * @method FS.File.prototype.name + * @public + * @param {String|null} [value] - If setting the name, specify the new name as the first argument. Otherwise the options argument should be first. + * @param {Object} [options] + * @param {Object} [options.store=none,original] - Get or set the name of the version of the file that was saved in this store. Default is the original file name. + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only. + * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only. + * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file name. + */ +FS.File.prototype.name = function(value, options) { + var self = this; + + if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) { + // GET + options = value || {}; + options = options.hash || options; // allow use as UI helper + return self._getInfo(options.store, options).name; + } else { + // SET + options = options || {}; + return self._setInfo(options.store, 'name', value, typeof options.save === "boolean" ? options.save : true); + } +}; + +/** + * @method FS.File.prototype.extension + * @public + * @param {String|null} [value] - If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first. + * @param {Object} [options] + * @param {Object} [options.store=none,original] - Get or set the extension of the version of the file that was saved in this store. Default is the original file extension. + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only. + * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only. + * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one. + */ +FS.File.prototype.extension = function(value, options) { + var self = this; + + if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) { + // GET + options = value || {}; + return FS.Utility.getFileExtension(self.name(options) || ''); + } else { + // SET + options = options || {}; + var newName = FS.Utility.setFileExtension(self.name(options) || '', value); + return self._setInfo(options.store, 'name', newName, typeof options.save === "boolean" ? options.save : true); + } +}; + +/** + * @method FS.File.prototype.size + * @public + * @param {Number} [value] - If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first. + * @param {Object} [options] + * @param {Object} [options.store=none,original] - Get or set the size of the version of the file that was saved in this store. Default is the original file size. + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only. + * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only. + * @returns {Number|undefined} If setting, returns `undefined`. If getting, returns the file size. + */ +FS.File.prototype.size = function(value, options) { + var self = this; + + if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) { + // GET + options = value || {}; + options = options.hash || options; // allow use as UI helper + return self._getInfo(options.store, options).size; + } else { + // SET + options = options || {}; + return self._setInfo(options.store, 'size', value, typeof options.save === "boolean" ? options.save : true); + } +}; + +/** + * @method FS.File.prototype.type + * @public + * @param {String} [value] - If setting the type, specify the new type as the first argument. Otherwise the options argument should be first. + * @param {Object} [options] + * @param {Object} [options.store=none,original] - Get or set the type of the version of the file that was saved in this store. Default is the original file type. + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only. + * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only. + * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file type. + */ +FS.File.prototype.type = function(value, options) { + var self = this; + + if (!options && ((typeof value === "object" && value !== null) || typeof value === "undefined")) { + // GET + options = value || {}; + options = options.hash || options; // allow use as UI helper + return self._getInfo(options.store, options).type; + } else { + // SET + options = options || {}; + return self._setInfo(options.store, 'type', value, typeof options.save === "boolean" ? options.save : true); + } +}; + +/** + * @method FS.File.prototype.updatedAt + * @public + * @param {String} [value] - If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first. + * @param {Object} [options] + * @param {Object} [options.store=none,original] - Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date. + * @param {Boolean} [options.updateFileRecordFirst=false] Update this instance with data from the DB first? Applies to getter usage only. + * @param {Boolean} [options.save=true] Save change to database? Applies to setter usage only. + * @returns {String|undefined} If setting, returns `undefined`. If getting, returns the file's last updated date. + */ +FS.File.prototype.updatedAt = function(value, options) { + var self = this; + + if (!options && ((typeof value === "object" && value !== null && !(value instanceof Date)) || typeof value === "undefined")) { + // GET + options = value || {}; + options = options.hash || options; // allow use as UI helper + return self._getInfo(options.store, options).updatedAt; + } else { + // SET + options = options || {}; + return self._setInfo(options.store, 'updatedAt', value, typeof options.save === "boolean" ? options.save : true); + } +}; + +/** + * @method FS.File.onStoredCallback + * @summary Calls callback when the file is fully stored to the specify storeName + * @public + * @param {String} [storeName] - The name of the file store we want to get called when stored. + * @param {function} [callback] + */ +FS.File.prototype.onStoredCallback = function (storeName, callback) { + // Check file is not already stored + if (this.hasStored(storeName)) { + callback(); + return; + } + if (Meteor.isServer) { + // Listen to file stored events + // TODO Require thinking whether it is better to use observer for case of using multiple application instances, Ask for same image url while upload is being done. + this.on('stored', function (newStoreName) { + // If stored is completed to the specified store call callback + if (storeName === newStoreName) { + // Remove the specified file stored listener + this.removeListener('stored', arguments.callee); + callback(); + } + }.bind(this) + ); + } else { + var fileId = this._id, + collectionName = this.collectionName; + // Wait for file to be fully uploaded + Tracker.autorun(function (c) { + Meteor.call('_cfs_returnWhenStored', collectionName, fileId, storeName, function (error, result) { + if (result && result === true) { + c.stop(); + callback(); + } else { + Meteor.setTimeout(function () { + c.invalidate(); + }, 100); + } + }); + }); + } +}; + +/** + * @method FS.File.onStored + * @summary Function that returns when the file is fully stored to the specify storeName + * @public + * @param {String} storeName - The name of the file store we want to get called when stored. + * + * Function that returns when the file is fully stored to the specify storeName. + * + * For example needed if wanted to save the direct link to a file on s3 when fully uploaded. + */ +FS.File.prototype.onStored = function (arguments) { + var onStoredSync = Meteor.wrapAsync(this.onStoredCallback); + return onStoredSync.call(this, arguments); +}; + +function isBasicObject(obj) { + return (obj === Object(obj) && Object.getPrototypeOf(obj) === Object.prototype); +} + +// getPrototypeOf polyfill +if (typeof Object.getPrototypeOf !== "function") { + if (typeof "".__proto__ === "object") { + Object.getPrototypeOf = function(object) { + return object.__proto__; + }; + } else { + Object.getPrototypeOf = function(object) { + // May break if the constructor has been tampered with + return object.constructor.prototype; + }; + } +} + + diff --git a/packages/wekan-cfs-file/fsFile-server.js b/packages/wekan-cfs-file/fsFile-server.js new file mode 100644 index 000000000..d502d0295 --- /dev/null +++ b/packages/wekan-cfs-file/fsFile-server.js @@ -0,0 +1,361 @@ +/** + * Notes a details about a storage adapter failure within the file record + * @param {string} storeName + * @param {number} maxTries + * @return {undefined} + * @todo deprecate this + */ +FS.File.prototype.logCopyFailure = function(storeName, maxTries) { + var self = this; + + // hasStored will update from the fileRecord + if (self.hasStored(storeName)) { + throw new Error("logCopyFailure: invalid storeName"); + } + + // Make sure we have a temporary file saved since we will be + // trying the save again. + FS.TempStore.ensureForFile(self); + + var now = new Date(); + var currentCount = (self.failures && self.failures.copies && self.failures.copies[storeName] && typeof self.failures.copies[storeName].count === "number") ? self.failures.copies[storeName].count : 0; + maxTries = maxTries || 5; + + var modifier = {}; + modifier.$set = {}; + modifier.$set['failures.copies.' + storeName + '.lastAttempt'] = now; + if (currentCount === 0) { + modifier.$set['failures.copies.' + storeName + '.firstAttempt'] = now; + } + modifier.$set['failures.copies.' + storeName + '.count'] = currentCount + 1; + modifier.$set['failures.copies.' + storeName + '.doneTrying'] = (currentCount + 1 >= maxTries); + self.update(modifier); +}; + +/** + * Has this store permanently failed? + * @param {String} storeName The name of the store + * @return {boolean} Has this store failed permanently? + * @todo deprecate this + */ +FS.File.prototype.failedPermanently = function(storeName) { + var self = this; + return !!(self.failures && + self.failures.copies && + self.failures.copies[storeName] && + self.failures.copies[storeName].doneTrying); +}; + +/** + * @method FS.File.prototype.createReadStream + * @public + * @param {String} [storeName] + * @returns {stream.Readable} Readable NodeJS stream + * + * Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name. + * + * * If you pass a `storeName`, a readable stream for the file data saved in that store is returned. + * * If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned. + * * If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned. + * + */ +FS.File.prototype.createReadStream = function(storeName) { + var self = this; + + // If we dont have a store name but got Buffer data? + if (!storeName && self.data) { + FS.debug && console.log("fileObj.createReadStream creating read stream for attached data"); + // Stream from attached data if present + return self.data.createReadStream(); + } else if (!storeName && FS.TempStore && FS.TempStore.exists(self)) { + FS.debug && console.log("fileObj.createReadStream creating read stream for temp store"); + // Stream from temp store - its a bit slower than regular streams? + return FS.TempStore.createReadStream(self); + } else { + // Stream from the store using storage adapter + if (self.isMounted()) { + var storage = self.collection.storesLookup[storeName] || self.collection.primaryStore; + FS.debug && console.log("fileObj.createReadStream creating read stream for store", storage.name); + // return stream + return storage.adapter.createReadStream(self); + } else { + throw new Meteor.Error('File not mounted'); + } + + } +}; + +/** + * @method FS.File.prototype.createWriteStream + * @public + * @param {String} [storeName] + * @returns {stream.Writeable} Writeable NodeJS stream + * + * Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name. + * + * * If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned. + * * If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned. + * + */ +FS.File.prototype.createWriteStream = function(storeName) { + var self = this; + + // We have to have a mounted file in order for this to work + if (self.isMounted()) { + if (!storeName && FS.TempStore && FS.FileWorker) { + // If we have worker installed - we pass the file to FS.TempStore + // We dont need the storeName since all stores will be generated from + // TempStore. + // This should trigger FS.FileWorker at some point? + FS.TempStore.createWriteStream(self); + } else { + // Stream directly to the store using storage adapter + var storage = self.collection.storesLookup[storeName] || self.collection.primaryStore; + return storage.adapter.createWriteStream(self); + } + } else { + throw new Meteor.Error('File not mounted'); + } +}; + +/** + * @method FS.File.prototype.copy Makes a copy of the file and underlying data in all stores. + * @public + * @returns {FS.File} The new FS.File instance + */ +FS.File.prototype.copy = function() { + var self = this; + + if (!self.isMounted()) { + throw new Error("Cannot copy a file that is not associated with a collection"); + } + + // Get the file record + var fileRecord = self.collection.files.findOne({_id: self._id}, {transform: null}) || {}; + + // Remove _id and copy keys from the file record + delete fileRecord._id; + + // Insert directly; we don't have access to "original" in this case + var newId = self.collection.files.insert(fileRecord); + + var newFile = self.collection.findOne(newId); + + // Copy underlying files in the stores + var mod, oldKey; + for (var name in newFile.copies) { + if (newFile.copies.hasOwnProperty(name)) { + oldKey = newFile.copies[name].key; + if (oldKey) { + // We need to ask the adapter for the true oldKey because + // right now gridfs does some extra stuff. + // TODO GridFS should probably set the full key object + // (with _id and filename) into `copies.key` + // so that copies.key can be passed directly to + // createReadStreamForFileKey + var sourceFileStorage = self.collection.storesLookup[name]; + if (!sourceFileStorage) { + throw new Error(name + " is not a valid store name"); + } + oldKey = sourceFileStorage.adapter.fileKey(self); + // delete so that new fileKey will be generated in copyStoreData + delete newFile.copies[name].key; + mod = mod || {}; + mod["copies." + name + ".key"] = copyStoreData(newFile, name, oldKey); + } + } + } + // Update keys in the filerecord + if (mod) { + newFile.update({$set: mod}); + } + + return newFile; +}; + +Meteor.methods({ + // Does a HEAD request to URL to get the type, updatedAt, + // and size prior to actually downloading the data. + // That way we can do filter checks without actually downloading. + '_cfs_getUrlInfo': function (url, options) { + check(url, String); + check(options, Object); + + this.unblock(); + + var response = HTTP.call("HEAD", url, options); + var headers = response.headers; + var result = {}; + + if (headers['content-type']) { + result.type = headers['content-type']; + } + + if (headers['content-length']) { + result.size = +headers['content-length']; + } + + if (headers['last-modified']) { + result.updatedAt = new Date(headers['last-modified']); + } + + return result; + }, + // Helper function that checks whether given fileId from collectionName + // Is fully uploaded to specify storeName. + '_cfs_returnWhenStored' : function (collectionName, fileId, storeName) { + check(collectionName, String); + check(fileId, String); + check(storeName, String); + + var collection = FS._collections[collectionName]; + if (!collection) { + return Meteor.Error('_cfs_returnWhenStored: FSCollection name not exists'); + } + + var file = collection.findOne({_id: fileId}); + if (!file) { + return Meteor.Error('_cfs_returnWhenStored: FSFile not exists'); + } + return file.hasStored(storeName); + } +}); + +// TODO maybe this should be in cfs-storage-adapter +function _copyStoreData(fileObj, storeName, sourceKey, callback) { + if (!fileObj.isMounted()) { + throw new Error("Cannot copy store data for a file that is not associated with a collection"); + } + + var storage = fileObj.collection.storesLookup[storeName]; + if (!storage) { + throw new Error(storeName + " is not a valid store name"); + } + + // We want to prevent beforeWrite and transformWrite from running, so + // we interact directly with the store. + var destinationKey = storage.adapter.fileKey(fileObj); + var readStream = storage.adapter.createReadStreamForFileKey(sourceKey); + var writeStream = storage.adapter.createWriteStreamForFileKey(destinationKey); + + writeStream.once('stored', function(result) { + callback(null, result.fileKey); + }); + + writeStream.once('error', function(error) { + callback(error); + }); + + readStream.pipe(writeStream); +} +var copyStoreData = Meteor.wrapAsync(_copyStoreData); + +/** + * @method FS.File.prototype.copyData Copies the content of a store directly into another store. + * @public + * @param {string} sourceStoreName + * @param {string} targetStoreName + * @param {boolean=} move + */ +FS.File.prototype.copyData = function(sourceStoreName, targetStoreName, move){ + + move = !!move; + /** + * @type {Object.} + */ + var sourceStoreValues = this.copies[sourceStoreName]; + /** + * @type {string} + */ + var copyKey = cloneDataToStore(this, sourceStoreName, targetStoreName, move); + /** + * @type {Object.} + */ + var targetStoreValues = {}; + for (var v in sourceStoreValues) { + if (sourceStoreValues.hasOwnProperty(v)) { + targetStoreValues[v] = sourceStoreValues[v] + } + } + targetStoreValues.key = copyKey; + targetStoreValues.createdAt = new Date(); + targetStoreValues.updatedAt = new Date(); + /** + * + * @type {modifier} + */ + var modifier = {}; + modifier.$set = {}; + modifier.$set["copies."+targetStoreName] = targetStoreValues; + if(move){ + modifier.$unset = {}; + modifier.$unset["copies."+sourceStoreName] = ""; + } + this.update(modifier); +}; +/** + * @method FS.File.prototype.moveData Moves the content of a store directly into another store. + * @public + * @param {string} sourceStoreName + * @param {string} targetStoreName + */ +FS.File.prototype.moveData = function(sourceStoreName, targetStoreName){ + this.copyData(sourceStoreName, targetStoreName, true); +}; +// TODO maybe this should be in cfs-storage-adapter +/** + * + * @param {FS.File} fileObj + * @param {string} sourceStoreName + * @param {string} targetStoreName + * @param {boolean} move + * @param callback + * @private + */ +function _copyDataFromStoreToStore(fileObj, sourceStoreName, targetStoreName, move, callback) { + if (!fileObj.isMounted()) { + throw new Error("Cannot copy store data for a file that is not associated with a collection"); + } + /** + * @type {FS.StorageAdapter} + */ + var sourceStorage = fileObj.collection.storesLookup[sourceStoreName]; + /** + * @type {FS.StorageAdapter} + */ + var targetStorage = fileObj.collection.storesLookup[targetStoreName]; + + if (!sourceStorage) { + throw new Error(sourceStoreName + " is not a valid store name"); + } + if (!targetStorage) { + throw new Error(targetStorage + " is not a valid store name"); + } + + // We want to prevent beforeWrite and transformWrite from running, so + // we interact directly with the store. + var sourceKey = sourceStorage.adapter.fileKey(fileObj); + var targetKey = targetStorage.adapter.fileKey(fileObj); + var readStream = sourceStorage.adapter.createReadStreamForFileKey(sourceKey); + var writeStream = targetStorage.adapter.createWriteStreamForFileKey(targetKey); + + + writeStream.safeOnce('stored', function(result) { + if(move && sourceStorage.adapter.remove(fileObj)===false){ + callback("Copied to store:" + targetStoreName + + " with fileKey: " + + result.fileKey + + ", but could not delete from source store: " + + sourceStoreName); + }else{ + callback(null, result.fileKey); + } + }); + + writeStream.once('error', function(error) { + callback(error); + }); + + readStream.pipe(writeStream); +} +var cloneDataToStore = Meteor.wrapAsync(_copyDataFromStoreToStore); diff --git a/packages/wekan-cfs-file/internal.api.md b/packages/wekan-cfs-file/internal.api.md new file mode 100644 index 000000000..362c9a622 --- /dev/null +++ b/packages/wekan-cfs-file/internal.api.md @@ -0,0 +1,749 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["fsFile-common.js"](fsFile-common.js) Where: {client|server}__ + +*** + +### new *fs*.File([ref])  Anywhere ### + +*This method __File__ is defined in `FS`* + +__Arguments__ + +* __ref__ *{object|[FS.File](#FS.File)|[data to attach](#data to attach)}* (Optional) + + Another FS.File instance, a filerecord, or some data to pass to attachData + + + +> ```FS.File = function(ref, createdByTransform) { ...``` [fsFile-common.js:8](fsFile-common.js#L8) + + +- + +### *fsFile*.attachData(data, [options], [callback])  Anywhere ### + +*This method __attachData__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __data__ *{[File](#File)|[Blob](#Blob)|Buffer|ArrayBuffer|Uint8Array|String}* + + The data that you want to attach to the file. + +* __options__ *{Object}* (Optional) + + Options + + * __type__ *{String}* (Optional) + + The data content (MIME) type, if known. + + * __headers__ *{String}* (Optional) + + When attaching a URL, headers to be used for the GET request (currently server only) + + * __auth__ *{String}* (Optional) + + When attaching a URL, "username:password" to be used for the GET request (currently server only) + +* __callback__ *{Function}* (Optional) + + Callback function, callback(error). On the client, a callback is required if data is a URL. + + +__Returns__ *{FS.File}* +This FS.File instance. + + + +> ```FS.File.prototype.attachData = function fsFileAttachData(data, options, callback) { ...``` [fsFile-common.js:36](fsFile-common.js#L36) + + +- + +### *fsFile*.uploadProgress()  Anywhere ### + +*This method __uploadProgress__ is defined in `prototype` of `FS.File`* + +__Returns__ *{number}* +The server confirmed upload progress + + +> ```FS.File.prototype.uploadProgress = function() { ...``` [fsFile-common.js:154](fsFile-common.js#L154) + + +- + +### *fsFile*.controlledByDeps()  Anywhere ### + +*This method __controlledByDeps__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns true if this FS.File is reactive + + +> Note: Returns true if this FS.File object was created by a FS.Collection +> and we are in a reactive computations. What does this mean? Well it should +> mean that our fileRecord is fully updated by Meteor and we are mounted on +> a collection + +> ```FS.File.prototype.controlledByDeps = function() { ...``` [fsFile-common.js:179](fsFile-common.js#L179) + + +- + +### *fsFile*.getCollection()  Anywhere ### + +*This method __getCollection__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns attached collection or undefined if not mounted + + +> ```FS.File.prototype.getCollection = function() { ...``` [fsFile-common.js:189](fsFile-common.js#L189) + + +- + +### *fsFile*.isMounted()  Anywhere ### + +*This method __isMounted__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.Collection}* +Returns attached collection or undefined if not mounted + + +> ```FS.File.prototype.isMounted = FS.File.prototype.getCollection;``` [fsFile-common.js:217](fsFile-common.js#L217) + + +- + +### *fsFile*.getFileRecord()  Anywhere ### + +*This method __getFileRecord__ is defined in `prototype` of `FS.File`* + +__Returns__ *{object}* +The filerecord + + +> ```FS.File.prototype.getFileRecord = function() { ...``` [fsFile-common.js:224](fsFile-common.js#L224) + + +- + +### *fsFile*.update(modifier, [options], [callback])  Anywhere ### + +*This method __update__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __modifier__ *{[modifier](#modifier)}* +* __options__ *{object}* (Optional) +* __callback__ *{function}* (Optional) + + +Updates the fileRecord. + +> ```FS.File.prototype.update = function(modifier, options, callback) { ...``` [fsFile-common.js:255](fsFile-common.js#L255) + + +- + +### *fsFile*._saveChanges([what])  Anywhere ### + +*This method is private* +*This method ___saveChanges__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __what__ *{String}* (Optional) + + "_original" to save original info, or a store name to save info for that store, or saves everything + + + +Updates the fileRecord from values currently set on the FS.File instance. + +> ```FS.File.prototype._saveChanges = function(what) { ...``` [fsFile-common.js:290](fsFile-common.js#L290) + + +- + +### *fsFile*.remove([callback])  Anywhere ### + +*This method __remove__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __callback__ *{Function}* (Optional) + +__Returns__ *{number}* +Count + + +Remove the current file from its FS.Collection + +> ```FS.File.prototype.remove = function(callback) { ...``` [fsFile-common.js:323](fsFile-common.js#L323) + + +- + +### *fsFile*.moveTo(targetCollection)  Anywhere ### + +*This method is private* +*This method __moveTo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __targetCollection__ *{[FS.Collection](#FS.Collection)}* + +__TODO__ +``` +* Needs to be implemented +``` + + +Move the file from current collection to another collection + +> Note: Not yet implemented + +> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364) + + +- + +### *fsFile*.getExtension([options])  Anywhere ### + +> __Warning!__ +> This method "FS.File.prototype.getExtension" has deprecated from the API +> Use the `extension` getter/setter method instead. + +*This method __getExtension__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{Object}* (Optional) + * __store__ *{String}* (Optional) + + Store name. Default is the original extension. + + +__Returns__ *{string}* +The extension eg.: `jpg` or if not found then an empty string '' + + +> ```FS.File.prototype.getExtension = function(options) { ...``` [fsFile-common.js:364](fsFile-common.js#L364) + + +- + +### *fsFile*.isImage([options])  Anywhere ### + +*This method __isImage__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has an image +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isImage = function(options) { ...``` [fsFile-common.js:393](fsFile-common.js#L393) + + +- + +### *fsFile*.isVideo([options])  Anywhere ### + +*This method __isVideo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has a video +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isVideo = function(options) { ...``` [fsFile-common.js:408](fsFile-common.js#L408) + + +- + +### *fsFile*.isAudio([options])  Anywhere ### + +*This method __isAudio__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __options__ *{object}* (Optional) + * __store__ *{string}* (Optional) + + The store we're interested in + + + +Returns true if the copy of this file in the specified store has an audio +content type. If the file object is unmounted or doesn't have a copy for +the specified store, or if you don't specify a store, this method checks +the content type of the original file. + +> ```FS.File.prototype.isAudio = function(options) { ...``` [fsFile-common.js:423](fsFile-common.js#L423) + + +- + +### *fsFile*.formattedSize({Object}, {String}, {String})  Anywhere ### + +*This method __formattedSize__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __{Object}__ *{any}* + + options + +* __{String}__ *{any}* + + [options.store=none,display original file size] Which file do you want to get the size of? + +* __{String}__ *{any}* + + [options.formatString='0.00 b'] The `numeral` format string to use. + + +__Returns__ *{String}* +The file size formatted as a human readable string and reactively updated. + + +You must add the `numeral` package to your app before you can use this method. +If info is not found or a size can't be determined, it will show 0. + +> ```FS.File.prototype.formattedSize = function fsFileFormattedSize(options) { ...``` [fsFile-common.js:438](fsFile-common.js#L438) + + +- + +### *fsFile*.isUploaded()  Anywhere ### + +*This method __isUploaded__ is defined in `prototype` of `FS.File`* + +__Returns__ *{boolean}* +True if the number of uploaded bytes is equal to the file size. + + +> ```FS.File.prototype.isUploaded = function() { ...``` [fsFile-common.js:456](fsFile-common.js#L456) + + +- + +### *fsFile*.hasStored(storeName, [optimistic])  Anywhere ### + +*This method __hasStored__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{string}* + + Name of the store + +* __optimistic__ *{boolean}* (Optional, Default = false) + + In case that the file record is not found, read below + + +__Returns__ *{boolean}* +Is a version of this file stored in the given store? + + +> Note: If the file is not published to the client or simply not found: +this method cannot know for sure if it exists or not. The `optimistic` +param is the boolean value to return. Are we `optimistic` that the copy +could exist. This is the case in `FS.File.url` we are optimistic that the +copy supplied by the user exists. + +> ```FS.File.prototype.hasStored = function(storeName, optimistic) { ...``` [fsFile-common.js:478](fsFile-common.js#L478) + + +- + +### *fsFile*.getCopyInfo(storeName)  Anywhere ### + +> __Warning!__ +> This method "FS.File.prototype.getCopyInfo" has deprecated from the API +> Use individual methods with `store` option instead. + +*This method __getCopyInfo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{string}* + + Name of the store for which to get copy info. + + +__Returns__ *{Object}* +The file details, e.g., name, size, key, etc., specific to the copy saved in this store. + + +> ```FS.File.prototype.getCopyInfo = function(storeName) { ...``` [fsFile-common.js:504](fsFile-common.js#L504) + + +- + +### *fsFile*._getInfo([storeName], [options])  Anywhere ### + +*This method is private* +*This method ___getInfo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* (Optional) + + Name of the store for which to get file info. Omit for original file details. + +* __options__ *{Object}* (Optional) + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? + + +__Returns__ *{Object}* +The file details, e.g., name, size, key, etc. If not found, returns an empty object. + + +> ```FS.File.prototype._getInfo = function(storeName, options) { ...``` [fsFile-common.js:519](fsFile-common.js#L519) + + +- + +### *fsFile*._setInfo(storeName, property, value, save)  Anywhere ### + +*This method is private* +*This method ___setInfo__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* + + Name of the store for which to set file info. Non-string will set original file details. + +* __property__ *{String}* + + Property to set + +* __value__ *{String}* + + New value for property + +* __save__ *{Boolean}* + + Should the new value be saved to the DB, too, or just set in the FS.File properties? + + +__Returns__ *{undefined}* + + +> ```FS.File.prototype._setInfo = function(storeName, property, value, save) { ...``` [fsFile-common.js:544](fsFile-common.js#L544) + + +- + +### *fsFile*.name([value], [options])  Anywhere ### + +*This method __name__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String|null}* (Optional) + + If setting the name, specify the new name as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the name of the version of the file that was saved in this store. Default is the original file name. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file name. + + +> ```FS.File.prototype.name = function(value, options) { ...``` [fsFile-common.js:568](fsFile-common.js#L568) + + +- + +### *fsFile*.extension([value], [options])  Anywhere ### + +*This method __extension__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String|null}* (Optional) + + If setting the extension, specify the new extension (without period) as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the extension of the version of the file that was saved in this store. Default is the original file extension. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file extension or an empty string if there isn't one. + + +> ```FS.File.prototype.extension = function(value, options) { ...``` [fsFile-common.js:593](fsFile-common.js#L593) + + +- + +### *fsFile*.size([value], [options])  Anywhere ### + +*This method __size__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{Number}* (Optional) + + If setting the size, specify the new size in bytes as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the size of the version of the file that was saved in this store. Default is the original file size. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{Number|undefined}* +If setting, returns `undefined`. If getting, returns the file size. + + +> ```FS.File.prototype.size = function(value, options) { ...``` [fsFile-common.js:618](fsFile-common.js#L618) + + +- + +### *fsFile*.type([value], [options])  Anywhere ### + +*This method __type__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String}* (Optional) + + If setting the type, specify the new type as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the type of the version of the file that was saved in this store. Default is the original file type. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file type. + + +> ```FS.File.prototype.type = function(value, options) { ...``` [fsFile-common.js:643](fsFile-common.js#L643) + + +- + +### *fsFile*.updatedAt([value], [options])  Anywhere ### + +*This method __updatedAt__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __value__ *{String}* (Optional) + + If setting updatedAt, specify the new date as the first argument. Otherwise the options argument should be first. + +* __options__ *{Object}* (Optional) + * __store__ *{Object}* (Optional, Default = none,original) + + Get or set the last updated date for the version of the file that was saved in this store. Default is the original last updated date. + + * __updateFileRecordFirst__ *{Boolean}* (Optional, Default = false) + + Update this instance with data from the DB first? Applies to getter usage only. + + * __save__ *{Boolean}* (Optional, Default = true) + + Save change to database? Applies to setter usage only. + + +__Returns__ *{String|undefined}* +If setting, returns `undefined`. If getting, returns the file's last updated date. + + +> ```FS.File.prototype.updatedAt = function(value, options) { ...``` [fsFile-common.js:668](fsFile-common.js#L668) + + +*** + +__File: ["fsFile-server.js"](fsFile-server.js) Where: {server}__ + +*** + +### *fsFile*.logCopyFailure(storeName, maxTries)  Server ### + +``` +Notes a details about a storage adapter failure within the file record +``` +*This method __logCopyFailure__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{string}* +* __maxTries__ *{number}* + +__Returns__ *{undefined}* + +__TODO__ +``` +* deprecate this +``` + + +> ```FS.File.prototype.logCopyFailure = function(storeName, maxTries) { ...``` [fsFile-server.js:8](fsFile-server.js#L8) + + +- + +### *fsFile*.failedPermanently(storeName)  Server ### + +``` +Has this store permanently failed? +``` +*This method __failedPermanently__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* + + The name of the store + + +__Returns__ *{boolean}* +Has this store failed permanently? + +__TODO__ +``` +* deprecate this +``` + + +> ```FS.File.prototype.failedPermanently = function(storeName) { ...``` [fsFile-server.js:41](fsFile-server.js#L41) + + +- + +### *fsFile*.createReadStream([storeName])  Server ### + +*This method __createReadStream__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* (Optional) + +__Returns__ *{stream.Readable}* +Readable NodeJS stream + + +Returns a readable stream. Where the stream reads from depends on the FS.File instance and whether you pass a store name. + +If you pass a `storeName`, a readable stream for the file data saved in that store is returned. +If you don't pass a `storeName` and data is attached to the FS.File instance (on `data` property, which must be a DataMan instance), then a readable stream for the attached data is returned. +If you don't pass a `storeName` and there is no data attached to the FS.File instance, a readable stream for the file data currently in the temporary store (`FS.TempStore`) is returned. + + +> ```FS.File.prototype.createReadStream = function(storeName) { ...``` [fsFile-server.js:62](fsFile-server.js#L62) + + +- + +### *fsFile*.createWriteStream([storeName])  Server ### + +*This method __createWriteStream__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __storeName__ *{String}* (Optional) + +__Returns__ *{stream.Writeable}* +Writeable NodeJS stream + + +Returns a writeable stream. Where the stream writes to depends on whether you pass in a store name. + +If you pass a `storeName`, a writeable stream for (over)writing the file data in that store is returned. +If you don't pass a `storeName`, a writeable stream for writing to the temp store for this file is returned. + + +> ```FS.File.prototype.createWriteStream = function(storeName) { ...``` [fsFile-server.js:100](fsFile-server.js#L100) + + +- + +### *fsFile*.copy()  Server ### + +*This method __copy__ is defined in `prototype` of `FS.File`* + +__Returns__ *{FS.File}* +The new FS.File instance + + +> ```FS.File.prototype.copy = function() { ...``` [fsFile-server.js:126](fsFile-server.js#L126) + + diff --git a/packages/wekan-cfs-file/package.js b/packages/wekan-cfs-file/package.js new file mode 100644 index 000000000..d14cc069f --- /dev/null +++ b/packages/wekan-cfs-file/package.js @@ -0,0 +1,55 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-cfs-file.git', + name: 'wekan-cfs-file', + version: '0.1.17', + summary: 'CollectionFS, FS.File object' +}); + +Npm.depends({ + temp: "0.7.0" // for tests only +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + // This imply is needed for tests, and is technically probably correct anyway. + api.imply([ + 'wekan-cfs-base-package@0.0.30' + ]); + + api.use([ + 'wekan-cfs-base-package@0.0.30', + 'wekan-cfs-storage-adapter@0.2.1', + 'tracker', + 'check', + 'ddp', + 'mongo', + 'http', + 'wekan-cfs-data-man@0.0.6', + 'raix:eventemitter@0.1.1' + ]); + + api.addFiles([ + 'fsFile-common.js' + ], 'client'); + + api.addFiles([ + 'fsFile-common.js', + 'fsFile-server.js' + ], 'server'); +}); + +Package.onTest(function (api) { + api.use([ + 'wekan-cfs-standard-packages@0.0.0', + 'wekan-cfs-gridfs@0.0.0', + 'tinytest@1.0.0', + 'http@1.0.0', + 'test-helpers@1.0.0', + 'wekan-cfs-http-methods@0.0.29' + ]); + + api.addFiles([ + 'tests/file-tests.js' + ]); +}); diff --git a/packages/wekan-cfs-file/tests/file-tests.js b/packages/wekan-cfs-file/tests/file-tests.js new file mode 100644 index 000000000..7ad3310e4 --- /dev/null +++ b/packages/wekan-cfs-file/tests/file-tests.js @@ -0,0 +1,436 @@ +function bin2str(bufView) { + var length = bufView.length; + var result = ''; + for (var i = 0; i length) { + addition = length - i; + } + try { + // this fails on phantomjs due to old webkit bug; hence the try/catch + result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition)); + } catch (e) { + var dataArray = []; + for (var j = i; j < i+addition; j++) { + dataArray.push(bufView[j]); + } + result += String.fromCharCode.apply(null, dataArray); + } + } + return result; +} + +//function ab2str(buffer) { +// return bin2str(new Uint8Array(buffer)); +//} + +function str2ab(str) { + var buf = new ArrayBuffer(str.length); + var bufView = new Uint8Array(buf); + for (var i=0, strLen=str.length; inew *fsStore*.GridFS(name, options)  Server ### + +*This method __GridFS__ is defined in `FS.Store`* + +__Arguments__ + +* __name__ *{String}* + + The store name + +* __options__ *{Object}* + * __beforeSave__ *{Function}* (Optional) + + Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + + * __maxTries__ *{Number}* (Optional, Default = 5) + + Max times to attempt saving a file + + +__Returns__ *{FS.StorageAdapter}* +An instance of FS.StorageAdapter. + + +Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter +type. + +> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16) + + +- + +### new *fsStore*.GridFS(name, options)  Client ### + +*This method __GridFS__ is defined in `FS.Store`* + +__Arguments__ + +* __name__ *{String}* + + The store name + +* __options__ *{Object}* + * __beforeSave__ *{Function}* (Optional) + + Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + + * __maxTries__ *{Number}* (Optional, Default = 5) + + Max times to attempt saving a file + + +__Returns__ *{undefined}* + + +Creates a GridFS store instance on the client, which is just a shell object +storing some info. + +> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13) + + diff --git a/packages/wekan-cfs-gridfs/gridfs.client.js b/packages/wekan-cfs-gridfs/gridfs.client.js new file mode 100644 index 000000000..3cb26583b --- /dev/null +++ b/packages/wekan-cfs-gridfs/gridfs.client.js @@ -0,0 +1,21 @@ +/** + * @public + * @constructor + * @param {String} name - The store name + * @param {Object} options + * @param {Function} [options.beforeSave] - Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + * @param {Number} [options.maxTries=5] - Max times to attempt saving a file + * @returns {undefined} + * + * Creates a GridFS store instance on the client, which is just a shell object + * storing some info. + */ +FS.Store.GridFS = function(name, options) { + var self = this; + if (!(self instanceof FS.Store.GridFS)) + throw new Error('FS.Store.GridFS missing keyword "new"'); + + return new FS.StorageAdapter(name, options, { + typeName: 'storage.gridfs' + }); +}; diff --git a/packages/wekan-cfs-gridfs/gridfs.server.js b/packages/wekan-cfs-gridfs/gridfs.server.js new file mode 100644 index 000000000..90b92e0ca --- /dev/null +++ b/packages/wekan-cfs-gridfs/gridfs.server.js @@ -0,0 +1,176 @@ +var path = Npm.require('path'); +var mongodb = Npm.require('mongodb'); +var ObjectID = Npm.require('mongodb').ObjectID; +var Grid = Npm.require('gridfs-stream'); +//var Grid = Npm.require('gridfs-locking-stream'); + +var chunkSize = 1024*1024*2; // 256k is default GridFS chunk size, but performs terribly for largish files + +/** + * @public + * @constructor + * @param {String} name - The store name + * @param {Object} options + * @param {Function} [options.beforeSave] - Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + * @param {Number} [options.maxTries=5] - Max times to attempt saving a file + * @returns {FS.StorageAdapter} An instance of FS.StorageAdapter. + * + * Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter + * type. + */ + +FS.Store.GridFS = function(name, options) { + var self = this; + options = options || {}; + + var gridfsName = name; + var mongoOptions = options.mongoOptions || {}; + + if (!(self instanceof FS.Store.GridFS)) + throw new Error('FS.Store.GridFS missing keyword "new"'); + + if (!options.mongoUrl) { + options.mongoUrl = process.env.MONGO_URL; + // When using a Meteor MongoDB instance, preface name with "cfs_gridfs." + gridfsName = "cfs_gridfs." + name; + } + + if (!options.mongoOptions) { + options.mongoOptions = { db: { native_parser: true }, server: { auto_reconnect: true }}; + } + + if (options.chunkSize) { + chunkSize = options.chunkSize; + } + + return new FS.StorageAdapter(name, options, { + + typeName: 'storage.gridfs', + fileKey: function(fileObj) { + // We should not have to mount the file here - We assume its taken + // care of - Otherwise we create new files instead of overwriting + var key = { + _id: null, + filename: null + }; + + // If we're passed a fileObj, we retrieve the _id and filename from it. + if (fileObj) { + var info = fileObj._getInfo(name, {updateFileRecordFirst: false}); + key._id = info.key || null; + key.filename = info.name || fileObj.name({updateFileRecordFirst: false}) || (fileObj.collectionName + '-' + fileObj._id); + } + + // If key._id is null at this point, createWriteStream will let GridFS generate a new ID + return key; + }, + createReadStream: function(fileKey, options) { + options = options || {}; + + // Init GridFS + var gfs = new Grid(self.db, mongodb); + + // Set the default streamning settings + var settings = { + _id: new ObjectID(fileKey._id), + root: gridfsName + }; + + // Check if this should be a partial read + if (typeof options.start !== 'undefined' && typeof options.end !== 'undefined' ) { + // Add partial info + settings.range = { + startPos: options.start, + endPos: options.end + }; + } + + FS.debug && console.log('GRIDFS', settings); + + return gfs.createReadStream(settings); + + }, + createWriteStream: function(fileKey, options) { + options = options || {}; + + // Init GridFS + var gfs = new Grid(self.db, mongodb); + + var opts = { + filename: fileKey.filename, + mode: 'w', + root: gridfsName, + chunk_size: options.chunk_size || chunkSize, + // We allow aliases, metadata and contentType to be passed in via + // options + aliases: options.aliases || [], + metadata: options.metadata || null, + content_type: options.contentType || 'application/octet-stream' + }; + + if (fileKey._id) { + opts._id = new ObjectID(fileKey._id); + } + + var writeStream = gfs.createWriteStream(opts); + + writeStream.on('close', function(file) { + if (!file) { + // gridfs-stream will emit "close" without passing a file + // if there is an error. We can simply exit here because + // the "error" listener will also be called in this case. + return; + } + + if (FS.debug) console.log('SA GridFS - DONE!'); + + // Emit end and return the fileKey, size, and updated date + writeStream.emit('stored', { + // Set the generated _id so that we know it for future reads and writes. + // We store the _id as a string and only convert to ObjectID right before + // reading, writing, or deleting. If we store the ObjectID itself, + // Meteor (EJSON?) seems to convert it to a LocalCollection.ObjectID, + // which GFS doesn't understand. + fileKey: file._id.toString(), + size: file.length, + storedAt: file.uploadDate || new Date() + }); + }); + + writeStream.on('error', function(error) { + console.log('SA GridFS - ERROR!', error); + }); + + return writeStream; + + }, + remove: function(fileKey, callback) { + // Init GridFS + var gfs = new Grid(self.db, mongodb); + + try { + gfs.remove({ _id: new ObjectID(fileKey._id), root: gridfsName }, callback); + } catch(err) { + callback(err); + } + }, + + // Not implemented + watch: function() { + throw new Error("GridFS storage adapter does not support the sync option"); + }, + + init: function(callback) { + mongodb.MongoClient.connect(options.mongoUrl, mongoOptions, function (err, db) { + if (err) { return callback(err); } + self.db = db; + + // ensure that indexes are added as otherwise CollectionFS fails for Mongo >= 3.0 + var collection = new Mongo.Collection(gridfsName); + collection.rawCollection().ensureIndex({ "files_id": 1, "n": 1}); + + callback(null); + }); + } + }); +}; diff --git a/packages/wekan-cfs-gridfs/internal.api.md b/packages/wekan-cfs-gridfs/internal.api.md new file mode 100644 index 000000000..cb81e7439 --- /dev/null +++ b/packages/wekan-cfs-gridfs/internal.api.md @@ -0,0 +1,75 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["gridfs.server.js"](gridfs.server.js) Where: {server}__ + +*** + +### new *fsStore*.GridFS(name, options)  Server ### + +*This method __GridFS__ is defined in `FS.Store`* + +__Arguments__ + +* __name__ *{String}* + + The store name + +* __options__ *{Object}* + * __beforeSave__ *{Function}* (Optional) + + Function to run before saving a file from the server. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + + * __maxTries__ *{Number}* (Optional, Default = 5) + + Max times to attempt saving a file + + +__Returns__ *{FS.StorageAdapter}* +An instance of FS.StorageAdapter. + + +Creates a GridFS store instance on the server. Inherits from FS.StorageAdapter +type. + +> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.server.js:16](gridfs.server.js#L16) + + +*** + +__File: ["gridfs.client.js"](gridfs.client.js) Where: {client}__ + +*** + +### new *fsStore*.GridFS(name, options)  Client ### + +*This method __GridFS__ is defined in `FS.Store`* + +__Arguments__ + +* __name__ *{String}* + + The store name + +* __options__ *{Object}* + * __beforeSave__ *{Function}* (Optional) + + Function to run before saving a file from the client. The context of the function will be the `FS.File` instance we're saving. The function may alter its properties. + + * __maxTries__ *{Number}* (Optional, Default = 5) + + Max times to attempt saving a file + + +__Returns__ *{undefined}* + + +Creates a GridFS store instance on the client, which is just a shell object +storing some info. + +> ```FS.Store.GridFS = function(name, options) { ...``` [gridfs.client.js:13](gridfs.client.js#L13) + + diff --git a/packages/wekan-cfs-gridfs/package.js b/packages/wekan-cfs-gridfs/package.js new file mode 100755 index 000000000..6c430f22a --- /dev/null +++ b/packages/wekan-cfs-gridfs/package.js @@ -0,0 +1,24 @@ +Package.describe({ + name: 'wekan-cfs-gridfs', + version: '0.0.34', + summary: 'GridFS storage adapter for CollectionFS', + git: 'https://github.com/zcfs/Meteor-cfs-gridfs.git' +}); + +Npm.depends({ + mongodb: '2.2.9', + 'gridfs-stream': '1.1.1' + //'gridfs-locking-stream': '0.0.3' +}); + +Package.onUse(function (api) { + api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-storage-adapter@0.2.3', 'ecmascript@0.1.0']); + api.addFiles('gridfs.server.js', 'server'); + api.addFiles('gridfs.client.js', 'client'); +}); + +Package.onTest(function (api) { + api.use(['wekan-cfs-gridfs', 'test-helpers', 'tinytest'], 'server'); + api.addFiles('tests/server-tests.js', 'server'); + api.addFiles('tests/client-tests.js', 'client'); +}); diff --git a/packages/wekan-cfs-gridfs/tests/client-tests.js b/packages/wekan-cfs-gridfs/tests/client-tests.js new file mode 100644 index 000000000..222af562f --- /dev/null +++ b/packages/wekan-cfs-gridfs/tests/client-tests.js @@ -0,0 +1,44 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-gridfs - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Client Tests + * + * construct FS.File with no arguments + * construct FS.File passing in File + * construct FS.File passing in Blob + * load blob into FS.File and then call FS.File.toDataUrl + * call FS.File.setDataFromBinary, then FS.File.getBlob(); make sure correct data is returned + * load blob into FS.File and then call FS.File.getBinary() with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * load blob into FS.File and make sure FS.File.saveLocal initiates a download (possibly can't do automatically) + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-gridfs/tests/server-tests.js b/packages/wekan-cfs-gridfs/tests/server-tests.js new file mode 100644 index 000000000..61a7a3f92 --- /dev/null +++ b/packages/wekan-cfs-gridfs/tests/server-tests.js @@ -0,0 +1,49 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-gridfs - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Server Tests + * + * construct FS.File with no arguments + * load data with FS.File.setDataFromBuffer + * load data with FS.File.setDataFromBinary + * load data and then call FS.File.toDataUrl with and without callback + * load buffer into FS.File and then call FS.File.getBinary with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * (call these with and without callback to test sync vs. async) + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * + * + * FS.Collection Server Tests + * + * Make sure options.filter is respected + * + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-http-methods/.editorconfig b/packages/wekan-cfs-http-methods/.editorconfig new file mode 100644 index 000000000..a2cc1c1fe --- /dev/null +++ b/packages/wekan-cfs-http-methods/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +# Meteor adapted EditorConfig, http://EditorConfig.org +# By RaiX 2013 + +root = true + +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto +# curly_bracket_next_line = true \ No newline at end of file diff --git a/packages/wekan-cfs-http-methods/.gitignore b/packages/wekan-cfs-http-methods/.gitignore new file mode 100644 index 000000000..361e39a8f --- /dev/null +++ b/packages/wekan-cfs-http-methods/.gitignore @@ -0,0 +1,2 @@ +.build* +/.versions \ No newline at end of file diff --git a/packages/wekan-cfs-http-methods/.jshintrc b/packages/wekan-cfs-http-methods/.jshintrc new file mode 100644 index 000000000..b288c9edc --- /dev/null +++ b/packages/wekan-cfs-http-methods/.jshintrc @@ -0,0 +1,140 @@ +{ + // JSHint Meteor Configuration File + // Match the Meteor Style Guide + // + // By @raix with contributions from @aldeed and @awatson1978 + // Source https://github.com/raix/Meteor-jshintrc + // + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // false: Identifiers do not need to be in camelCase. We would like to enforce this except for when interacting with our api objects which use snakeCase properties. + "curly" : false, // false: Do not require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : false, // false: Do not require all functions to be run in ES5 Strict Mode + "trailing" : true, // true: Prohibit trailing whitespaces + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : 250, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : true, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + //"meteor" : false, // Meteor.js + + // Legacy + "nomen" : false, // true: Prohibit dangling `_` in variables + "onevar" : false, // true: Allow only one `var` statement per function + "passfail" : false, // true: Stop on first error + "white" : false, // true: Check against strict whitespace and indentation rules + + // Custom globals, from http://docs.meteor.com, in the order they appear there + "globals" : { + "Meteor": false, + "DDP": false, + "Mongo": false, + "Session": false, + "Accounts": false, + "Template": false, + "Blaze": false, + "Spacebars": false, + "Match": false, + "check": false, + "Tracker": false, + "ReactiveVar": false, + "ReactiveDict": false, + "EJSON": false, + "HTTP": false, + "Email": false, + "Assets": false, + "Package": false, + "App": false, //mobile-config.js + "cordova": false, + "Cordova": false, + "Buffer": false, + + // Meteor internals + "DDPServer": false, + "global": false, + "Log": false, + "MongoInternals": false, + "process": false, + "Retry": false, + "WebApp": false, + "WebAppInternals": false, + + // globals useful when creating Meteor packages + "Npm": false, + "Tinytest": false, + + // common Meteor packages + "HTTP": true, + "Random": false, + "_": false, // Underscore.js + "$": false, // jQuery + + // This package + "_methodHTTP": true, + "Fiber": true, + "runServerMethod": true + } +} diff --git a/packages/wekan-cfs-http-methods/.travis.yml b/packages/wekan-cfs-http-methods/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-http-methods/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-http-methods/CHANGELOG.md b/packages/wekan-cfs-http-methods/CHANGELOG.md new file mode 100644 index 000000000..425243944 --- /dev/null +++ b/packages/wekan-cfs-http-methods/CHANGELOG.md @@ -0,0 +1,152 @@ +# Changelog + +## vCurrent + +## [v0.0.30] +#### 9/9/15 by Eric Dobbertin +- Ensure we do not try to end responses twice + +## [v0.0.29] +#### 29/4/15 by Eric Dobbertin +- Respond properly to 206 Range requests (thanks [cherbst](https://github.com/cherbst)) + +## [v0.0.28] +#### 8/4/15 by Eric Dobbertin +- Fix issue where response would not be sent when using streams + +## [v0.0.26] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.26) +#### 17/12/14 by Morten Henriksen +- mbr update, remove versions.json + +## [v0.0.25] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.25) +#### 17/12/14 by Morten Henriksen +- mbr update versions and fix warnings + +- *Merged pull-request:* "- Added link to documentation about usage of HTTP client-side" [#22](https://github.com/zcfs/Meteor-http-methods/issues/22) ([jkaan](https://github.com/jkaan)) + +- *Merged pull-request:* "Typos." [#23](https://github.com/zcfs/Meteor-http-methods/issues/23) ([bradvogel](https://github.com/bradvogel)) + +- - Added link to documentation about usage of HTTP client-side + +- *Merged pull-request:* "typo in example: this.params instead of this.param" [#21](https://github.com/zcfs/Meteor-http-methods/issues/21) ([lukasvan3l](https://github.com/lukasvan3l)) + +- typo in example: this.params instead of this.param + +- *Merged pull-request:* ""code" is not always there" [#20](https://github.com/zcfs/Meteor-http-methods/issues/20) ([lukasvan3l](https://github.com/lukasvan3l)) + +- code is not always there + +- 0.9.1 support + +Patches by GitHub users [@jkaan](https://github.com/jkaan), [@bradvogel](https://github.com/bradvogel), [@lukasvan3l](https://github.com/lukasvan3l). + +## [v0.0.23] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.23) +#### 25/08/14 by Morten Henriksen +- *Fixed bug:* "Cannot transfer a file stream over 5Mb to the server." [#17](https://github.com/zcfs/Meteor-http-methods/issues/17) + +- *Merged pull-request:* "Add this.requestHeaders to readme" [#12](https://github.com/zcfs/Meteor-http-methods/issues/12) ([matthewsimo](https://github.com/matthewsimo)) + +- Add this.requestHeaders to readme + +Patches by GitHub user [@matthewsimo](https://github.com/matthewsimo). + +## [v0.0.22] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.22) +#### 25/03/14 by Morten Henriksen +- *Merged pull-request:* "Typo fixes" [#11](https://github.com/zcfs/Meteor-http-methods/issues/11) ([dandv](https://github.com/dandv)) + +- *Merged pull-request:* "Cosmetic fixes" [#10](https://github.com/zcfs/Meteor-http-methods/issues/10) ([dandv](https://github.com/dandv)) + +- Typo fixes + +- Cosmetic fixes + +Patches by GitHub user [@dandv](https://github.com/dandv). + +## [v0.0.21] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.21) +#### 23/03/14 by Morten Henriksen +## [v0.0.20] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.20) +#### 22/03/14 by Morten Henriksen +- add check + +- use collectionFS travis version force update + +## [v0.0.19] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.19) +#### 22/03/14 by Morten Henriksen +- Added ability for throwing http errors + +## [v0.0.18] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.18) +#### 18/03/14 by Morten Henriksen +- support HEAD requests + +- Add better streaming api and fix timeout / chunk size bug + +- refactor + added read stream + +- Add streaming WIP + +- Name tests by package + +- unbreak http-publish tests when run at in one test + +## [v0.0.17] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.17) +#### 22/02/14 by Morten Henriksen +- *Fixed bug:* "Binary use currently breaks json usage of data" [#6](https://github.com/zcfs/Meteor-http-methods/issues/6) + +## [v0.0.16] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.16) +#### 22/02/14 by Morten Henriksen +- Fix test - params from a query string is string now + +- make sure falsy turns into string + +## [v0.0.15] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.15) +#### 21/02/14 by Eric Dobbertin +## [v0.0.14] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.14) +#### 20/02/14 by Eric Dobbertin +- support binary data from the request + +## [v0.0.13] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.13) +#### 17/02/14 by Morten Henriksen +- Merge branch 'master' of https://github.com/zcfs/Meteor-http-methods + +- Bump to version 0.0.12 + +## [v0.0.12] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.12) +#### 17/02/14 by Eric Dobbertin +- Add requestHeaders to method context + +- Better handling of Meteor.Error's + +- url decode values in params + +- Allow user to set headers in custom response + +## [v0.0.11] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.11) +#### 08/12/13 by Morten Henriksen +## [v0.0.10] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.10) +#### 14/11/13 by Morten Henriksen +## [v0.0.9] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.9) +#### 30/09/13 by Morten Henriksen +- Add MIT License + +## [v0.0.8] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.8) +#### 28/09/13 by Morten Henriksen +- Comment feature - format selector as extension in url + +## [v0.0.7] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.7) +#### 18/09/13 by Morten Henriksen +## [v0.0.6, tag: v0.0.5] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.6, tag: v0.0.5) +#### 18/09/13 by Morten Henriksen +- Added more features + +## [v0.0.4] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.4) +#### 13/09/13 by Morten Henriksen +## [v0.0.3] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.3) +#### 12/09/13 by Morten Henriksen +## [v0.0.2] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.2) +#### 12/09/13 by Morten Henriksen +- *Fixed bug:* "allow empty last value in url" [#1](https://github.com/zcfs/Meteor-http-methods/issues/1) + +## [v0.0.1] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.1) +#### 12/09/13 by Morten Henriksen +- Init commit + diff --git a/packages/wekan-cfs-http-methods/LICENSE.md b/packages/wekan-cfs-http-methods/LICENSE.md new file mode 100644 index 000000000..2a5c5339e --- /dev/null +++ b/packages/wekan-cfs-http-methods/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-http-methods/README.md b/packages/wekan-cfs-http-methods/README.md new file mode 100644 index 000000000..b253ff48b --- /dev/null +++ b/packages/wekan-cfs-http-methods/README.md @@ -0,0 +1,215 @@ +wekan-cfs-http-methods [![Build Status](https://travis-ci.org/CollectionFS/Meteor-http-methods.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-http-methods) +============ + +~~Looking for maintainers - please reach out!~~ +This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible. + +**If you're looking for an alternative, we highly recommend [Meteor-Files](https://github.com/VeliovGroup/Meteor-Files) by [VeliovGroup](https://github.com/VeliovGroup)** + +--- + +Add server-side methods to the `HTTP` object your app. It's a server-side package only *- no client simulations added.* + +## Usage + +The `HTTP` object gets a `methods` method: + +```js + HTTP.methods({ + 'list': function() { + return 'Default content type is text/html'; + } + }); +``` + +## Methods scope +The methods scope contains different kinds of inputs. We can also get user details if logged in. + + +* `this.userId` - the user whose id and token was used to run this method, if set/found +* `this.method` - `GET`, `POST`, `PUT`, `DELETE` +* `this.query` - query params `?token=1&id=2` -> `{ token: 1, id: 2 }` +* `this.params` - set params `/foo/:name/test/:id` -> `{ name: '', id: '' }` +* `this.userAgent` - get the user agent string set in the request header +* `this.requestHeaders` - request headers object +* `this.setUserId(id)` - option for setting the `this.userId` +* `this.isSimulation` - always false on the server +* `this.unblock` - not implemented +* `this.setContentType('text/html')` - set the content type in header, defaults to `text/html` +* `this.addHeader('Content-Disposition', 'attachment; filename="name.ext"')` +* `this.setStatusCode(200)` - set the status code in the response header +* `createReadStream` - if a request, then get the read stream +* `createWriteStream` - if you want to stream data to the client +* `Error` - when streaming we have to be able to send error and close connection +* `this.request` The original request object + +## Passing data via header + +From the client: +```js + HTTP.post('list', { + data: { foo: 'bar' } + }, function(err, result) { + console.log('Content: ' + result.content + ' === "Hello"'); + }); +``` + +HTTP Server method: +```js + HTTP.methods({ + 'list': function(data) { + if (data.foo === 'bar') { + /* data we pass via the header is parsed by EJSON.parse + If not able, then it returns the raw data instead */ + } + return 'Hello'; + } + }); +``` + +## Parameters +The method name or URL can be used to pass parameters to the method. The parameters are available on the server under `this.params`: + +Client +```js + HTTP.post('/items/12/emails/5', function(err, result) { + console.log('Got back: ' + result.content); + }); +``` + +Server +```js + HTTP.methods({ + '/items/:itemId/emails/:emailId': function() { + // this.params.itemId === '12' + // this.params.emailId === '5' + } + }); +``` + +## Extended usage +The `HTTP.methods` normally takes a function, but it can be set to an object for fine-grained handling. + +Example: +```js + HTTP.methods({ + '/hello': { + get: function(data) {}, + // post: function(data) {}, + // put: function(data) {}, + // delete: function(data) {}, + // options: function() { + // // Example of a simple options function + // this.setStatusCode(200); + // this.addHeader('Accept', 'POST,PUT'); + // // The options for this restpoint + // var options = { + // POST: { + // description: 'Create an issue', + // parameters: { + // title: { + // type: 'string', + // description: 'Issue title' + // } + // } + // } + // }; + // // Print the options in pretty json + // return JSON.stringify(options, null, '\t'); + // }, + // stream: true // flag whether to allow stream handling in the request + } + }); +``` +*In this example the mounted http rest point will only support the `get` method* + +Example: +```js + HTTP.methods({ + '/hello': { + method: function(data) {}, + } + }); +``` +*In this example all methods `get`, `put`, `post`, `delete` will use the same function - This would be equal to setting the function directly on the http mount point* + +## Authentication + +The client needs the `access_token` to login in HTTP methods. *One could create a HTTP login/logout method for allowing pure external access* + +Client +```js + HTTP.post('/hello', { + params: { + token: Accounts && Accounts._storedLoginToken() + } + }, function(err, result) { + console.log('Got back: ' + result.content); + }); +``` + +Server +```js + 'hello': function(data) { + if (this.userId) { + var user = Meteor.users.findOne({ _id: this.userId }); + return 'Hello ' + (user && user.username || user && user.emails[0].address || 'user'); + } else { + this.setStatusCode(401); // Unauthorized + } + } +``` + +## Using custom authentication + +It's possible to make your own function to set the userId - not using the built-in token pattern. +```js + // My auth will return the userId + var myAuth = function() { + // Read the token from '/hello?token=5' + var userToken = self.query.token; + // Check the userToken before adding it to the db query + // Set the this.userId + if (userToken) { + var user = Meteor.users.findOne({ 'services.resume.loginTokens.token': userToken }); + + // Set the userId in the scope + return user && user._id; + } + }; + + HTTP.methods({ + '/hello': { + auth: myAuth, + method: function(data) { + // this.userId is set by myAuth + if (this.userId) { /**/ } else { /**/ } + } + } + }); +``` +*The above resembles the builtin auth handler* + +## Security +When buffering data instead of streaming we set the buffer limit to 5mb - This can be changed on the fly: +```js + // Set the max data length + // 5mb = 5 * 1024 * 1024 = 5242880; + HTTP.methodsMaxDataLength = 5242880; +``` + +## Login and logout (TODO) + +These operations are not currently supported for off Meteor use - there are some security considerations. + +`basic-auth` is broadly supported, but: +* password should not be sent in clear text - hash with base64? +* should be used on https connections +* Its difficult / impossible to logout a user? + +`token` the current `access_token` seems to be a better solution. Better control and options to logout users. But calling the initial `login` method still requires: +* hashing of password +* use https + +## HTTP Client-side usage +If you want to use the HTTP client-side functionality and find yourself having a hard time viewing all available options; these can be found on https://docs.meteor.com/#/full/http. diff --git a/packages/wekan-cfs-http-methods/http.methods.client.api.js b/packages/wekan-cfs-http-methods/http.methods.client.api.js new file mode 100644 index 000000000..d6dd694a9 --- /dev/null +++ b/packages/wekan-cfs-http-methods/http.methods.client.api.js @@ -0,0 +1,6 @@ +HTTP = Package.http && Package.http.HTTP || {}; + +// Client-side simulation is not yet implemented +HTTP.methods = function() { + throw new Error('HTTP.methods not implemented on client-side'); +}; diff --git a/packages/wekan-cfs-http-methods/http.methods.server.api.js b/packages/wekan-cfs-http-methods/http.methods.server.api.js new file mode 100644 index 000000000..fc3b9d116 --- /dev/null +++ b/packages/wekan-cfs-http-methods/http.methods.server.api.js @@ -0,0 +1,644 @@ +/* + +GET /note +GET /note/:id +POST /note +PUT /note/:id +DELETE /note/:id + +*/ +HTTP = Package.http && Package.http.HTTP || {}; + +// Primary local test scope +_methodHTTP = {}; + + +_methodHTTP.methodHandlers = {}; +_methodHTTP.methodTree = {}; + +// This could be changed eg. could allow larger data chunks than 1.000.000 +// 5mb = 5 * 1024 * 1024 = 5242880; +HTTP.methodsMaxDataLength = 5242880; //1e6; + +_methodHTTP.nameFollowsConventions = function(name) { + // Check that name is string, not a falsy or empty + return name && name === '' + name && name !== ''; +}; + + +_methodHTTP.getNameList = function(name) { + // Remove leading and trailing slashes and make command array + name = name && name.replace(/^\//g, '') || ''; // /^\/|\/$/g + // TODO: Get the format from the url - eg.: "/list/45.json" format should be + // set in this function by splitting the last list item by . and have format + // as the last item. How should we toggle: + // "/list/45/item.name.json" and "/list/45/item.name"? + // We would either have to check all known formats or allways determin the "." + // as an extension. Resolving in "json" and "name" as handed format - the user + // Could simply just add the format as a parametre? or be explicit about + // naming + return name && name.split('/') || []; +}; + +// Merge two arrays one containing keys and one values +_methodHTTP.createObject = function(keys, values) { + var result = {}; + if (keys && values) { + for (var i = 0; i < keys.length; i++) { + result[keys[i]] = values[i] && decodeURIComponent(values[i]) || ''; + } + } + return result; +}; + +_methodHTTP.addToMethodTree = function(methodName) { + var list = _methodHTTP.getNameList(methodName); + var name = '/'; + // Contains the list of params names + var params = []; + var currentMethodTree = _methodHTTP.methodTree; + + for (var i = 0; i < list.length; i++) { + + // get the key name + var key = list[i]; + // Check if it expects a value + if (key[0] === ':') { + // This is a value + params.push(key.slice(1)); + key = ':value'; + } + name += key + '/'; + + // Set the key into the method tree + if (typeof currentMethodTree[key] === 'undefined') { + currentMethodTree[key] = {}; + } + + // Dig deeper + currentMethodTree = currentMethodTree[key]; + + } + + if (_.isEmpty(currentMethodTree[':ref'])) { + currentMethodTree[':ref'] = { + name: name, + params: params + }; + } + + return currentMethodTree[':ref']; +}; + +// This method should be optimized for speed since its called on allmost every +// http call to the server so we return null as soon as we know its not a method +_methodHTTP.getMethod = function(name) { + // Check if the + if (!_methodHTTP.nameFollowsConventions(name)) { + return null; + } + var list = _methodHTTP.getNameList(name); + // Check if we got a correct list + if (!list || !list.length) { + return null; + } + // Set current refernce in the _methodHTTP.methodTree + var currentMethodTree = _methodHTTP.methodTree; + // Buffer for values to hand on later + var values = []; + // Iterate over the method name and check if its found in the method tree + for (var i = 0; i < list.length; i++) { + // get the key name + var key = list[i]; + // We expect to find the key or :value if not we break + if (typeof currentMethodTree[key] !== 'undefined' || + typeof currentMethodTree[':value'] !== 'undefined') { + // We got a result now check if its a value + if (typeof currentMethodTree[key] === 'undefined') { + // Push the value + values.push(key); + // Set the key to :value to dig deeper + key = ':value'; + } + + } else { + // Break - method call not found + return null; + } + + // Dig deeper + currentMethodTree = currentMethodTree[key]; + } + + // Extract reference pointer + var reference = currentMethodTree && currentMethodTree[':ref']; + if (typeof reference !== 'undefined') { + return { + name: reference.name, + params: _methodHTTP.createObject(reference.params, values), + handle: _methodHTTP.methodHandlers[reference.name] + }; + } else { + // Did not get any reference to the method + return null; + } +}; + +// This method retrieves the userId from the token and makes sure that the token +// is valid and not expired +_methodHTTP.getUserId = function() { + var self = this; + + // // Get ip, x-forwarded-for can be comma seperated ips where the first is the + // // client ip + // var ip = self.req.headers['x-forwarded-for'] && + // // Return the first item in ip list + // self.req.headers['x-forwarded-for'].split(',')[0] || + // // or return the remoteAddress + // self.req.connection.remoteAddress; + + // Check authentication + var userToken = self.query.token; + + // Check if we are handed strings + try { + userToken && check(userToken, String); + } catch(err) { + throw new Meteor.Error(404, 'Error user token and id not of type strings, Error: ' + (err.stack || err.message)); + } + + // Set the this.userId + if (userToken) { + // Look up user to check if user exists and is loggedin via token + var user = Meteor.users.findOne({ + $or: [ + {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)}, + {'services.resume.loginTokens.token': userToken} + ] + }); + // TODO: check 'services.resume.loginTokens.when' to have the token expire + + // Set the userId in the scope + return user && user._id; + } + + return null; +}; + +// Expose the default auth for calling from custom authentication handlers. +HTTP.defaultAuth = _methodHTTP.getUserId; + +/* + +Add default support for options + +*/ +_methodHTTP.defaultOptionsHandler = function(methodObject) { + // List of supported methods + var allowMethods = []; + // The final result object + var result = {}; + + // Iterate over the methods + // XXX: We should have a way to extend this - We should have some schema model + // for our methods... + _.each(methodObject, function(f, methodName) { + // Skip the stream and auth functions - they are not public / accessible + if (methodName !== 'stream' && methodName !== 'auth') { + + // Create an empty description + result[methodName] = { description: '', parameters: {} }; + // Add method name to headers + allowMethods.push(methodName); + + } + }); + + // Lets play nice + this.setStatusCode(200); + + // We have to set some allow headers here + this.addHeader('Allow', allowMethods.join(',')); + + // Return json result - Pretty print + return JSON.stringify(result, null, '\t'); +}; + +// Public interface for adding server-side http methods - if setting a method to +// 'false' it would actually remove the method (can be used to unpublish a method) +HTTP.methods = function(newMethods) { + _.each(newMethods, function(func, name) { + if (_methodHTTP.nameFollowsConventions(name)) { + // Check if we got a function + //if (typeof func === 'function') { + var method = _methodHTTP.addToMethodTree(name); + // The func is good + if (typeof _methodHTTP.methodHandlers[method.name] !== 'undefined') { + if (func === false) { + // If the method is set to false then unpublish + delete _methodHTTP.methodHandlers[method.name]; + // Delete the reference in the _methodHTTP.methodTree + delete method.name; + delete method.params; + } else { + // We should not allow overwriting - following Meteor.methods + throw new Error('HTTP method "' + name + '" is already registered'); + } + } else { + // We could have a function or a object + // The object could have: + // '/test/': { + // auth: function() ... returning the userId using over default + // + // method: function() ... + // or + // post: function() ... + // put: + // get: + // delete: + // head: + // } + + /* + We conform to the object format: + { + auth: + post: + put: + get: + delete: + head: + } + This way we have a uniform reference + */ + + var uniObj = {}; + if (typeof func === 'function') { + uniObj = { + 'auth': _methodHTTP.getUserId, + 'stream': false, + 'POST': func, + 'PUT': func, + 'GET': func, + 'DELETE': func, + 'HEAD': func, + 'OPTIONS': _methodHTTP.defaultOptionsHandler + }; + } else { + uniObj = { + 'stream': func.stream || false, + 'auth': func.auth || _methodHTTP.getUserId, + 'POST': func.post || func.method, + 'PUT': func.put || func.method, + 'GET': func.get || func.method, + 'DELETE': func.delete || func.method, + 'HEAD': func.head || func.get || func.method, + 'OPTIONS': func.options || _methodHTTP.defaultOptionsHandler + }; + } + + // Registre the method + _methodHTTP.methodHandlers[method.name] = uniObj; // func; + + } + // } else { + // // We do require a function as a function to execute later + // throw new Error('HTTP.methods failed: ' + name + ' is not a function'); + // } + } else { + // We have to follow the naming spec defined in nameFollowsConventions + throw new Error('HTTP.method "' + name + '" invalid naming of method'); + } + }); +}; + +var sendError = function(res, code, message) { + if (code) { + res.writeHead(code); + } else { + res.writeHead(500); + } + res.end(message); +}; + +// This handler collects the header data into either an object (if json) or the +// raw data. The data is passed to the callback +var requestHandler = function(req, res, callback) { + if (typeof callback !== 'function') { + return null; + } + + // Container for buffers and a sum of the length + var bufferData = [], dataLen = 0; + + // Extract the body + req.on('data', function(data) { + bufferData.push(data); + dataLen += data.length; + + // We have to check the data length in order to spare the server + if (dataLen > HTTP.methodsMaxDataLength) { + dataLen = 0; + bufferData = []; + // Flood attack or faulty client + sendError(res, 413, 'Flood attack or faulty client'); + req.connection.destroy(); + } + }); + + // When message is ready to be passed on + req.on('end', function() { + if (res.finished) { + return; + } + + // Allow the result to be undefined if so + var result; + + // If data found the work it - either buffer or json + if (dataLen > 0) { + result = new Buffer(dataLen); + // Merge the chunks into one buffer + for (var i = 0, ln = bufferData.length, pos = 0; i < ln; i++) { + bufferData[i].copy(result, pos); + pos += bufferData[i].length; + delete bufferData[i]; + } + // Check if we could be dealing with json + if (result[0] == 0x7b && result[1] === 0x22) { + try { + // Convert the body into json and extract the data object + result = EJSON.parse(result.toString()); + } catch(err) { + // Could not parse so we return the raw data + } + } + } // Else result will be undefined + + try { + callback(result); + } catch(err) { + sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) ); + } + }); + +}; + +// This is the simplest handler - it simply passes req stream as data to the +// method +var streamHandler = function(req, res, callback) { + try { + callback(); + } catch(err) { + sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) ); + } +}; + +/* + Allow file uploads in cordova cfs +*/ +var setCordovaHeaders = function(request, response) { + var origin = request.headers.origin; + // Match http://localhost: for Cordova clients in Meteor 1.3 + // and http://meteor.local for earlier versions + if (origin && (origin === 'http://meteor.local' || /^http:\/\/localhost/.test(origin))) { + // We need to echo the origin provided in the request + response.setHeader("Access-Control-Allow-Origin", origin); + + response.setHeader("Access-Control-Allow-Methods", "PUT"); + response.setHeader("Access-Control-Allow-Headers", "Content-Type"); + } +}; + +// Handle the actual connection +WebApp.connectHandlers.use(function(req, res, next) { + + // Check to se if this is a http method call + var method = _methodHTTP.getMethod(req._parsedUrl.pathname); + + // If method is null then it wasn't and we pass the request along + if (method === null) { + return next(); + } + + var dataHandle = (method.handle && method.handle.stream)?streamHandler:requestHandler; + + dataHandle(req, res, function(data) { + // If methodsHandler not found or somehow the methodshandler is not a + // function then return a 404 + if (typeof method.handle === 'undefined') { + sendError(res, 404, 'Error HTTP method handler "' + method.name + '" is not found'); + return; + } + + // Set CORS headers for Meteor Cordova clients + setCordovaHeaders(req, res); + + // Set fiber scope + var fiberScope = { + // Pointers to Request / Response + req: req, + res: res, + // Request / Response helpers + statusCode: 200, + method: req.method, + // Headers for response + headers: { + 'Content-Type': 'text/html' // Set default type + }, + // Arguments + data: data, + query: req.query, + params: method.params, + // Method reference + reference: method.name, + methodObject: method.handle, + _streamsWaiting: 0 + }; + + // Helper functions this scope + Fiber = Npm.require('fibers'); + runServerMethod = Fiber(function(self) { + var result, resultBuffer; + + // We fetch methods data from methodsHandler, the handler uses the this.addItem() + // function to populate the methods, this way we have better check control and + // better error handling + messages + + // The scope for the user methodObject callbacks + var thisScope = { + // The user whos id and token was used to run this method, if set/found + userId: null, + // The id of the data + _id: null, + // Set the query params ?token=1&id=2 -> { token: 1, id: 2 } + query: self.query, + // Set params /foo/:name/test/:id -> { name: '', id: '' } + params: self.params, + // Method GET, PUT, POST, DELETE, HEAD + method: self.method, + // User agent + userAgent: req.headers['user-agent'], + // All request headers + requestHeaders: req.headers, + // Add the request object it self + request: req, + // Set the userId + setUserId: function(id) { + this.userId = id; + }, + // We dont simulate / run this on the client at the moment + isSimulation: false, + // Run the next method in a new fiber - This is default at the moment + unblock: function() {}, + // Set the content type in header, defaults to text/html? + setContentType: function(type) { + self.headers['Content-Type'] = type; + }, + setStatusCode: function(code) { + self.statusCode = code; + }, + addHeader: function(key, value) { + self.headers[key] = value; + }, + createReadStream: function() { + self._streamsWaiting++; + return req; + }, + createWriteStream: function() { + self._streamsWaiting++; + return res; + }, + Error: function(err) { + + if (err instanceof Meteor.Error) { + // Return controlled error + sendError(res, err.error, err.message); + } else if (err instanceof Error) { + // Return error trace - this is not intented + sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) ); + } else { + sendError(res, 503, 'Error in method "' + self.reference + '"' ); + } + + }, + // getData: function() { + // // XXX: TODO if we could run the request handler stuff eg. + // // in here in a fiber sync it could be cool - and the user did + // // not have to specify the stream=true flag? + // } + }; + + // This function sends the final response. Depending on the + // timing of the streaming, we might have to wait for all + // streaming to end, or we might have to wait for this function + // to finish after streaming ends. The checks in this function + // and the fact that we call it twice ensure that we will always + // send the response if we haven't sent an error response, but + // we will not send it too early. + function sendResponseIfDone() { + res.statusCode = self.statusCode; + // If no streams are waiting + if (self._streamsWaiting === 0 && + (self.statusCode === 200 || self.statusCode === 206) && + self.done && + !self._responseSent && + !res.finished) { + self._responseSent = true; + res.end(resultBuffer); + } + } + + var methodCall = self.methodObject[self.method]; + + // If the method call is set for the POST/PUT/GET or DELETE then run the + // respective methodCall if its a function + if (typeof methodCall === 'function') { + + // Get the userId - This is either set as a method specific handler and + // will allways default back to the builtin getUserId handler + try { + // Try to set the userId + thisScope.userId = self.methodObject.auth.apply(self); + } catch(err) { + sendError(res, err.error, (err.message || err.stack)); + return; + } + + // This must be attached before there's any chance of `createReadStream` + // or `createWriteStream` being called, which means before we do + // `methodCall.apply` below. + req.on('end', function() { + self._streamsWaiting--; + sendResponseIfDone(); + }); + + // Get the result of the methodCall + try { + if (self.method === 'OPTIONS') { + result = methodCall.apply(thisScope, [self.methodObject]) || ''; + } else { + result = methodCall.apply(thisScope, [self.data]) || ''; + } + } catch(err) { + if (err instanceof Meteor.Error) { + // Return controlled error + sendError(res, err.error, err.message); + } else { + // Return error trace - this is not intented + sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) ); + } + return; + } + + // Set headers + _.each(self.headers, function(value, key) { + // If value is defined then set the header, this allows for unsetting + // the default content-type + if (typeof value !== 'undefined') + res.setHeader(key, value); + }); + + // If OK / 200 then Return the result + if (self.statusCode === 200 || self.statusCode === 206) { + + if (self.method !== "HEAD") { + // Return result + if (typeof result === 'string') { + resultBuffer = new Buffer(result); + } else { + resultBuffer = new Buffer(JSON.stringify(result)); + } + + // Check if user wants to overwrite content length for some reason? + if (typeof self.headers['Content-Length'] === 'undefined') { + self.headers['Content-Length'] = resultBuffer.length; + } + + } + + self.done = true; + sendResponseIfDone(); + + } else { + // Allow user to alter the status code and send a message + sendError(res, self.statusCode, result); + } + + } else { + sendError(res, 404, 'Service not found'); + } + + + }); + // Run http methods handler + try { + runServerMethod.run(fiberScope); + } catch(err) { + sendError(res, 500, 'Error running the server http method handler, Error: ' + (err.stack || err.message)); + } + + }); // EO Request handler + + +}); diff --git a/packages/wekan-cfs-http-methods/http.methods.tests.js b/packages/wekan-cfs-http-methods/http.methods.tests.js new file mode 100644 index 000000000..7157feb18 --- /dev/null +++ b/packages/wekan-cfs-http-methods/http.methods.tests.js @@ -0,0 +1,117 @@ +function equals(a, b) { + return EJSON.stringify(a) === EJSON.stringify(b); +} + +Tinytest.add('http-methods - test environment', function(test) { + test.isTrue(typeof _methodHTTP !== 'undefined', 'test environment not initialized _methodHTTP'); + test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP'); + test.isTrue(typeof HTTP.methods !== 'undefined', 'test environment not initialized HTTP.methods'); + +}); + +Tinytest.add('http-methods - nameFollowsConventions', function(test) { + test.isFalse(_methodHTTP.nameFollowsConventions(), 'Tested methods naming convention 1'); + test.isFalse(_methodHTTP.nameFollowsConventions(''), 'Tested methods naming convention 2'); + test.isFalse(_methodHTTP.nameFollowsConventions({}), 'Tested methods naming convention 3'); + test.isFalse(_methodHTTP.nameFollowsConventions([1]), 'Tested methods naming convention 4'); + test.isFalse(_methodHTTP.nameFollowsConventions(-1), 'Tested methods naming convention 5'); + test.isFalse(_methodHTTP.nameFollowsConventions(1), 'Tested methods naming convention 6'); + test.isFalse(_methodHTTP.nameFollowsConventions(0.1), 'Tested methods naming convention 7'); + test.isFalse(_methodHTTP.nameFollowsConventions(-0.1), 'Tested methods naming convention 8'); + + test.isTrue(_methodHTTP.nameFollowsConventions('/test/test'), 'Tested methods naming convention leading slash'); + test.isTrue(_methodHTTP.nameFollowsConventions('test/test'), 'Tested methods naming convention'); +}); + +Tinytest.add('http-methods - getNameList', function(test) { + test.equal(EJSON.stringify(_methodHTTP.getNameList()), '[]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('')), '[]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('/')), '[]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('//')), '["",""]', 'Name list failed'); + + test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/')), '["1",""]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/2')), '["1","2"]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/:name/2')), '["1",":name","2"]', 'Name list failed'); + test.equal(EJSON.stringify(_methodHTTP.getNameList('/1//2')), '["1","","2"]', 'Name list failed'); +}); + + +Tinytest.add('http-methods - createObject', function(test) { + test.equal(EJSON.stringify(_methodHTTP.createObject()), '{}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(2, 4)), '{}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], [])), '{"foo":""}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], ['bar'])), '{"foo":"bar"}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], [3])), '{"foo":"3"}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], ['bar', 3])), '{"foo":"bar"}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['foo', 'foo'], ['bar', 3])), '{"foo":"3"}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject([''], ['bar', 3])), '{"":"bar"}', 'createObject failed'); + test.equal(EJSON.stringify(_methodHTTP.createObject(['', ''], ['bar', 3])), '{"":"3"}', 'createObject failed'); +}); + +Tinytest.add('http-methods - addToMethodTree', function(test) { + var original = _methodHTTP.methodTree; + _methodHTTP.methodTree = {}; + _methodHTTP.addToMethodTree('login'); + test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"login":{":ref":{"name":"/login/","params":[]}}}', 'addToMethodTree failed'); + + _methodHTTP.methodTree = {}; + _methodHTTP.addToMethodTree('/foo/bar'); + test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{"bar":{":ref":{"name":"/foo/bar/","params":[]}}}}', 'addToMethodTree failed'); + + _methodHTTP.methodTree = {}; + _methodHTTP.addToMethodTree('/foo/:name/bar'); + test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}}}}', 'addToMethodTree failed'); + + _methodHTTP.addToMethodTree('/foo/:name/bar'); + test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}}}}', 'addToMethodTree failed'); + + _methodHTTP.addToMethodTree('/foo/name/bar'); + test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}},"name":{"bar":{":ref":{"name":"/foo/name/bar/","params":[]}}}}}', 'addToMethodTree failed'); + + _methodHTTP.methodTree = original; +}); + +Tinytest.add('http-methods - getMethod', function(test) { + // Basic tests + test.equal(EJSON.stringify(_methodHTTP.getMethod('')), 'null', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('//')), 'null', 'getMethod failed'); + + _methodHTTP.addToMethodTree('login'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login')), '{"name":"/login/","params":{}}', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('/login')), '{"name":"/login/","params":{}}', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login/')), 'null', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('/login/')), 'null', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login/test')), 'null', 'getMethod failed'); + + _methodHTTP.addToMethodTree('/login/'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login')), '{"name":"/login/","params":{}}', 'getMethod failed'); + + // + + _methodHTTP.addToMethodTree('/login/foo'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login/foo')), '{"name":"/login/foo/","params":{}}', 'getMethod failed'); + + _methodHTTP.addToMethodTree('/login/:name/foo'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login/bar/foo')), '{"name":"/login/:value/foo/","params":{"name":"bar"}}', 'getMethod failed'); + test.equal(EJSON.stringify(_methodHTTP.getMethod('login/foo')), '{"name":"/login/foo/","params":{}}', 'getMethod failed'); + +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equal(actual, expected, message, not) +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) diff --git a/packages/wekan-cfs-http-methods/package.js b/packages/wekan-cfs-http-methods/package.js new file mode 100644 index 000000000..dc1266407 --- /dev/null +++ b/packages/wekan-cfs-http-methods/package.js @@ -0,0 +1,31 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-http-methods.git', + name: 'wekan-cfs-http-methods', + version: '0.0.32', + summary: 'Adds HTTP.methods RESTful' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use(['webapp', 'underscore', 'ejson'], 'server'); + + api.use('http', { weak: true }); + + api.export && api.export('HTTP'); + + api.export && api.export('_methodHTTP', { testOnly: true }); + + api.addFiles('http.methods.client.api.js', 'client'); + api.addFiles('http.methods.server.api.js', 'server'); + +}); + +Package.onTest(function (api) { + api.use('wekan-cfs-http-methods', ['server']); + api.use('test-helpers', 'server'); + api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', + 'random', 'deps']); + + api.addFiles('http.methods.tests.js', 'server'); +}); diff --git a/packages/wekan-cfs-http-publish/.editorconfig b/packages/wekan-cfs-http-publish/.editorconfig new file mode 100644 index 000000000..37a7d486e --- /dev/null +++ b/packages/wekan-cfs-http-publish/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +# Meteor adapted EditorConfig, http://EditorConfig.org +# By RaiX 2013 + +root = true + +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto +# curly_bracket_next_line = true diff --git a/packages/wekan-cfs-http-publish/.gitignore b/packages/wekan-cfs-http-publish/.gitignore new file mode 100644 index 000000000..29c66e308 --- /dev/null +++ b/packages/wekan-cfs-http-publish/.gitignore @@ -0,0 +1,4 @@ +/versions.json +.build* +smart.lock +/nbproject/ \ No newline at end of file diff --git a/packages/wekan-cfs-http-publish/.jshintrc b/packages/wekan-cfs-http-publish/.jshintrc new file mode 100644 index 000000000..52d24acf2 --- /dev/null +++ b/packages/wekan-cfs-http-publish/.jshintrc @@ -0,0 +1,97 @@ +{ + // JSHint Meteor Configuration File + // Match the Meteor Style Guide + // + // By @raix with contributions from @aldeed and @awatson1978 + // Source https://github.com/raix/Meteor-jshintrc + // + // See http://jshint.com/docs/ for more details + + "maxerr": 50, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "forin": true, + "immed": false, + "indent": 2, + "latedef": false, + "newcap": false, + "noarg": true, + "noempty": true, + "nonew": false, + "plusplus": false, + "quotmark": false, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "maxparams": false, + "maxdepth": false, + "maxstatements": false, + "maxcomplexity": false, + "maxlen": 80, + "asi": false, + "boss": false, + "debug": false, + "eqnull": false, + "es5": false, + "esnext": false, + "moz": false, + "evil": false, + "expr": false, + "funcscope": false, + "globalstrict": true, + "iterator": false, + "lastsemic": false, + "laxbreak": false, + "laxcomma": false, + "loopfunc": false, + "multistr": false, + "proto": false, + "scripturl": false, + "smarttabs": false, + "shadow": false, + "sub": false, + "supernew": false, + "validthis": false, + "browser": true, + "couch": false, + "devel": true, + "dojo": false, + "jquery": false, + "mootools": false, + "node": false, + "nonstandard": false, + "prototypejs": false, + "rhino": false, + "worker": false, + "wsh": false, + "yui": false, + "nomen": false, + "onevar": false, + "passfail": false, + "white": false, + "predef": [ + "Meteor", + "Accounts", + "Session", + "Template", + "check", + "Match", + "Deps", + "EJSON", + "Email", + "Package", + "Tinytest", + "Npm", + "Assets", + "Packages", + "process", + "LocalCollection", + "_", + "Random", + "HTTP", + "_methodHTTP" + ] +} \ No newline at end of file diff --git a/packages/wekan-cfs-http-publish/.travis.yml b/packages/wekan-cfs-http-publish/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-http-publish/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-http-publish/LICENSE.md b/packages/wekan-cfs-http-publish/LICENSE.md new file mode 100644 index 000000000..2a5c5339e --- /dev/null +++ b/packages/wekan-cfs-http-publish/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-http-publish/README.md b/packages/wekan-cfs-http-publish/README.md new file mode 100644 index 000000000..33ff66c49 --- /dev/null +++ b/packages/wekan-cfs-http-publish/README.md @@ -0,0 +1,129 @@ +wekan-cfs-http-publish [![Build Status](https://travis-ci.org/CollectionFS/Meteor-http-publish.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-http-publish) +============ + +This package add the ability to add `HTTP` server publish to your project. It's a server-side package only. + +DEPRECATING: Use https://atmospherejs.com/simple/rest instead + +## Usage +HTTP.publish creates a http crud restpoint for a collection *- only one cursor is allowed pr. publish* + +### Security +`CRUD+L` - Create Read Update Delete + List are common rest point operations. + +All `CUD` methods are the exact same as the `ddp` methods handlers - This means that `Meteor.allow` and `Meteor.deny` are setting the access rules for both `ddp` and `http` collection methods. + +All `R+L` methods are limited to the publish function. + +### Fully mounted +If handed a collection and a publish function the HTTP.publish will mount on follow urls and methods: +* `GET` - `/api/list` *- all published data* +* `POST` - `/api/list` *- insert a document into collection* +* `GET` - `/api/list/:id` *- find one published document* +* `PUT` - `/api/list/:id` *- update a document* +* `DELETE` - `/api/list/:id` *- remove a document* + +```js + myCollection = new Meteor.Collection('list'); + + // Add access points for `GET`, `POST`, `PUT`, `DELETE` + HTTP.publish({collection: myCollection}, function(data) { + // this.userId, this.query, this.params + return myCollection.find({}); + }); +``` + +### Publish view only +If handed a mount name and a publish function the HTTP.publish will mount: +* `GET` - `/mylist` *- all published data* + +```js + myCollection = new Meteor.Collection('list'); + + // Add access points for `GET` + HTTP.publish({name: 'mylist'}, function(data) { + // this.userId, this.query, this.params + return myCollection.find({}); + }); +``` + +### Create Update Delete only +If handed a collection only the HTTP.publish will mount: +* `POST` - `/api/list` *- insert a document into collection* +* `PUT` - `/api/list/:id` *- update a document* +* `DELETE` - `/api/list/:id` *- remove a document* + +```js + myCollection = new Meteor.Collection('list'); + + // Add access points for `POST`, `PUT`, `DELETE` + HTTP.publish({collection: myCollection}); +``` + +## Publish scope +The publish scope contains different kinds of inputs. We can also get user details if logged in. + +* `this.userId` The user whos id and token was used to run this method, if set/found +* `this.query` - query params `?token=1` -> { token: 1 } +* `this.params` - Set params /foo/:name/test/:id -> { name: '', id: '' } + +## Passing data via header +From the client: +```js + HTTP.get('/api/list', { + data: { foo: 'bar' } + }, function(err, result) { + console.log('Content in parsed json: '); + console.log(result.data); + }); +``` + +HTTP Server method: +```js + HTTP.publish({collection: myCollection}, function(data) { + // data === { foo: 'bar' } + }); +``` + +## Authentication +For details on authentication of http calls please read the [Authentication part in HTTP.methods package](https://github.com/raix/Meteor-http-methods#authentication) + +*The publish will have the `this.userId` set if an authenticated user is making the request.* + +## Format handlers +The query parametre `format` is used to set different output formats. The buildin format is `json` *(EJSON since we are on Meteor)* + +Example: *(`json` is buildin)* +```js + // Format the output into json + HTTP.publishFormats({ + 'json': function(result) { + // Set the method scope content type to json + this.setContentType('application/json'); + // Return EJSON string + return EJSON.stringify(result); + } + }); +``` + +`GET` url: `/api/list?format=json` +```js + HTTP.get('/api/list', { + params: { + format: 'json' + } + }, function(err, result) { + console.log('Back from update'); + if (err) { + console.log('Got error'); + } + console.log('Got json back: ' + result.content); + }); +``` + +## Unpublish +For `api` integrity theres added an `HTTP.unpublish` method that takes a collection or name of mount point to remove. + +## API Documentation + +[Here](api.md) diff --git a/packages/wekan-cfs-http-publish/api.md b/packages/wekan-cfs-http-publish/api.md new file mode 100644 index 000000000..b6ca5c8dd --- /dev/null +++ b/packages/wekan-cfs-http-publish/api.md @@ -0,0 +1,153 @@ +## http-publish Public API ## + +Adds HTTP.publish and HTTP.unpublish RESTful + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +- + +### _publishHTTP {any}  Server ### + +``` +GET /note +GET /note/:id +POST /note +PUT /note/:id +DELETE /note/:id +``` + +> ```_publishHTTP = { ...``` [http.publish.server.api.js:20](http.publish.server.api.js#L20) + + + +- +Could be cool if we could serve some api doc or even an api script +user could do and be served +a client-side javascript api? +Eg. +HTTP.api.note.create(); +HTTP.api.login(username, password); +HTTP.api.logout +- + +### *http*.publishFormats(newHandlers)  Server ### + +*This method __publishFormats__ is defined in `HTTP`* + +__Arguments__ + +* __newHandlers__ *{Object}* + +__Returns__ *{undefined}* + + +Add publish formats. Example: +```js +HTTP.publishFormats({ +json: function(inputObject) { +// Set the method scope content type to json +this.setContentType('application/json'); +// Return EJSON string +return EJSON.stringify(inputObject); +} +}); +``` + +> ```HTTP.publishFormats = function httpPublishFormats(newHandlers) { ...``` [http.publish.server.api.js:215](http.publish.server.api.js#L215) + + +- + +### *http*.publish(options, [name], [collection], [publishFunc])  Server ### + +*This method __publish__ is defined in `HTTP`* + +__Arguments__ + +* __options__ *{Object}* + * __defaultFormat__ *{String}* (Optional, Default = 'json') + + Format to use for responses when `format` is not found in the query string. + + * __collectionGet__ *{String}* (Optional, Default = true) + + Add GET restpoint for collection? Requires a publish function. + + * __collectionPost__ *{String}* (Optional, Default = true) + + Add POST restpoint for adding documents to the collection? + + * __documentGet__ *{String}* (Optional, Default = true) + + Add GET restpoint for documents in collection? Requires a publish function. + + * __documentPut__ *{String}* (Optional, Default = true) + + Add PUT restpoint for updating a document in the collection? + + * __documentDelete__ *{String}* (Optional, Default = true) + + Add DELETE restpoint for deleting a document in the collection? + +* __name__ *{String}* (Optional) + + Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default. + +* __collection__ *{[Meteor.Collection](#Meteor.Collection)}* (Optional) + + Meteor.Collection instance. Required for all restpoints except collectionGet + +* __publishFunc__ *{Function}* (Optional) + + A publish function. Required to mount GET restpoints. + + +__Returns__ *{undefined}* + + +Publishes one or more restpoints, mounted on "name" ("/api/collectionName/" +by default). The GET restpoints are subscribed to the document set (cursor) +returned by the publish function you supply. The other restpoints forward +requests to Meteor's built-in DDP methods (insert, update, remove), meaning +that full allow/deny security is automatic. + +__Usage:__ + +Publish only: + +HTTP.publish({name: 'mypublish'}, publishFunc); + +Publish and mount crud rest point for collection /api/myCollection: + +HTTP.publish({collection: myCollection}, publishFunc); + +Mount CUD rest point for collection and documents without GET: + +HTTP.publish({collection: myCollection}); + + +> ```HTTP.publish = function httpPublish(options, publishFunc) { ...``` [http.publish.server.api.js:256](http.publish.server.api.js#L256) + + +- + +### *http*.unpublish([name])  Server ### + +*This method __unpublish__ is defined in `HTTP`* + +__Arguments__ + +* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional) + + The method name or collection + + +__Returns__ *{undefined}* + + +Unpublishes all HTTP methods that were published with the given name or +for the given collection. Call with no arguments to unpublish all. + +> ```HTTP.unpublish = _publishHTTP.unpublish;``` [http.publish.server.api.js:453](http.publish.server.api.js#L453) + + diff --git a/packages/wekan-cfs-http-publish/http.publish.client.api.js b/packages/wekan-cfs-http-publish/http.publish.client.api.js new file mode 100644 index 000000000..62339351c --- /dev/null +++ b/packages/wekan-cfs-http-publish/http.publish.client.api.js @@ -0,0 +1,12 @@ +// Client-side is not implemented +HTTP.publish = function() { + throw new Error('HTTP.publish not implemented on client-side'); +}; + +HTTP.publishFormats = function() { + throw new Error('HTTP.publishFormats not implemented on client-side'); +}; + +HTTP.unpublish = function() { + throw new Error('HTTP.unpublish not implemented on client-side'); +}; diff --git a/packages/wekan-cfs-http-publish/http.publish.server.api.js b/packages/wekan-cfs-http-publish/http.publish.server.api.js new file mode 100644 index 000000000..6fcc44854 --- /dev/null +++ b/packages/wekan-cfs-http-publish/http.publish.server.api.js @@ -0,0 +1,466 @@ +/* + +GET /note +GET /note/:id +POST /note +PUT /note/:id +DELETE /note/:id + +*/ + +// Could be cool if we could serve some api doc or even an api script +// user could do and be served +// a client-side javascript api? +// Eg. +// HTTP.api.note.create(); +// HTTP.api.login(username, password); +// HTTP.api.logout + + +_publishHTTP = {}; + +// Cache the names of all http methods we've published +_publishHTTP.currentlyPublished = []; + +var defaultAPIPrefix = '/api/'; + +/** + * @method _publishHTTP.getPublishScope + * @private + * @param {Object} scope + * @returns {httpPublishGetPublishScope.publishScope} + * + * Creates a nice scope for the publish method + */ +_publishHTTP.getPublishScope = function httpPublishGetPublishScope(scope) { + var publishScope = {}; + publishScope.userId = scope.userId; + publishScope.params = scope.params; + publishScope.query = scope.query; + // TODO: Additional scoping + // publishScope.added + // publishScope.ready + return publishScope; +}; + +_publishHTTP.formatHandlers = {}; + +/** + * @method _publishHTTP.formatHandlers.json + * @private + * @param {Object} result - The result object + * @returns {String} JSON + * + * Formats the output into JSON and sets the appropriate content type on `this` + */ +_publishHTTP.formatHandlers.json = function httpPublishJSONFormatHandler(result) { + // Set the method scope content type to json + this.setContentType('application/json'); + // Return EJSON string + return EJSON.stringify(result); +}; + +/** + * @method _publishHTTP.formatResult + * @private + * @param {Object} result - The result object + * @param {Object} scope + * @param {String} [defaultFormat='json'] - Default format to use if format is not in query string. + * @returns {Any} The formatted result + * + * Formats the result into the format selected by querystring eg. "&format=json" + */ +_publishHTTP.formatResult = function httpPublishFormatResult(result, scope, defaultFormat) { + + // Get the format in lower case and default to json + var format = scope && scope.query && scope.query.format || defaultFormat || 'json'; + + // Set the format handler found + var formatHandlerFound = !!(typeof _publishHTTP.formatHandlers[format] === 'function'); + + // Set the format handler and fallback to default json if handler not found + var formatHandler = _publishHTTP.formatHandlers[(formatHandlerFound) ? format : 'json']; + + // Check if format handler is a function + if (typeof formatHandler !== 'function') { + // We break things the user could have overwritten the default json handler + throw new Error('The default json format handler not found'); + } + + if (!formatHandlerFound) { + scope.setStatusCode(500); + return '{"error":"Format handler for: `' + format + '` not found"}'; + } + + // Execute the format handler + try { + return formatHandler.apply(scope, [result]); + } catch(err) { + scope.setStatusCode(500); + return '{"error":"Format handler for: `' + format + '` Error: ' + err.message + '"}'; + } +}; + +/** + * @method _publishHTTP.error + * @private + * @param {String} statusCode - The status code + * @param {String} message - The message + * @param {Object} scope + * @returns {Any} The formatted result + * + * Responds with error message in the expected format + */ +_publishHTTP.error = function httpPublishError(statusCode, message, scope) { + var result = _publishHTTP.formatResult(message, scope); + scope.setStatusCode(statusCode); + return result; +}; + +/** + * @method _publishHTTP.getMethodHandler + * @private + * @param {Meteor.Collection} collection - The Meteor.Collection instance + * @param {String} methodName - The method name + * @returns {Function} The server method + * + * Returns the DDP connection handler, already setup and secured + */ +_publishHTTP.getMethodHandler = function httpPublishGetMethodHandler(collection, methodName) { + if (collection instanceof Meteor.Collection) { + if (collection._connection && collection._connection.method_handlers) { + return collection._connection.method_handlers[collection._prefix + methodName]; + } else { + throw new Error('HTTP publish does not work with current version of Meteor'); + } + } else { + throw new Error('_publishHTTP.getMethodHandler expected a collection'); + } +}; + +/** + * @method _publishHTTP.unpublishList + * @private + * @param {Array} names - List of method names to unpublish + * @returns {undefined} + * + * Unpublishes all HTTP methods that have names matching the given list. + */ +_publishHTTP.unpublishList = function httpPublishUnpublishList(names) { + if (!names.length) { + return; + } + + // Carry object for methods + var methods = {}; + + // Unpublish the rest points by setting them to false + for (var i = 0, ln = names.length; i < ln; i++) { + methods[names[i]] = false; + } + + HTTP.methods(methods); + + // Remove the names from our list of currently published methods + _publishHTTP.currentlyPublished = _.difference(_publishHTTP.currentlyPublished, names); +}; + +/** + * @method _publishHTTP.unpublish + * @private + * @param {String|Meteor.Collection} [name] - The method name or collection + * @returns {undefined} + * + * Unpublishes all HTTP methods that were published with the given name or + * for the given collection. Call with no arguments to unpublish all. + */ +_publishHTTP.unpublish = function httpPublishUnpublish(/* name or collection, options */) { + + // Determine what method name we're unpublishing + var name = (arguments[0] instanceof Meteor.Collection) ? + defaultAPIPrefix + arguments[0]._name : arguments[0]; + + // Unpublish name and name/id + if (name && name.length) { + _publishHTTP.unpublishList([name, name + '/:id']); + } + + // If no args, unpublish all + else { + _publishHTTP.unpublishList(_publishHTTP.currentlyPublished); + } + +}; + +/** + * @method HTTP.publishFormats + * @public + * @param {Object} newHandlers + * @returns {undefined} + * + * Add publish formats. Example: + ```js + HTTP.publishFormats({ + + json: function(inputObject) { + // Set the method scope content type to json + this.setContentType('application/json'); + // Return EJSON string + return EJSON.stringify(inputObject); + } + + }); + ``` + */ +HTTP.publishFormats = function httpPublishFormats(newHandlers) { + _.extend(_publishHTTP.formatHandlers, newHandlers); +}; + +/** + * @method HTTP.publish + * @public + * @param {Object} options + * @param {String} [name] - Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default. + * @param {Meteor.Collection} [collection] - Meteor.Collection instance. Required for all restpoints except collectionGet + * @param {String} [options.defaultFormat='json'] - Format to use for responses when `format` is not found in the query string. + * @param {String} [options.collectionGet=true] - Add GET restpoint for collection? Requires a publish function. + * @param {String} [options.collectionPost=true] - Add POST restpoint for adding documents to the collection? + * @param {String} [options.documentGet=true] - Add GET restpoint for documents in collection? Requires a publish function. + * @param {String} [options.documentPut=true] - Add PUT restpoint for updating a document in the collection? + * @param {String} [options.documentDelete=true] - Add DELETE restpoint for deleting a document in the collection? + * @param {Function} [publishFunc] - A publish function. Required to mount GET restpoints. + * @returns {undefined} + * @todo this should use options argument instead of optional args + * + * Publishes one or more restpoints, mounted on "name" ("/api/collectionName/" + * by default). The GET restpoints are subscribed to the document set (cursor) + * returned by the publish function you supply. The other restpoints forward + * requests to Meteor's built-in DDP methods (insert, update, remove), meaning + * that full allow/deny security is automatic. + * + * __Usage:__ + * + * Publish only: + * + * HTTP.publish({name: 'mypublish'}, publishFunc); + * + * Publish and mount crud rest point for collection /api/myCollection: + * + * HTTP.publish({collection: myCollection}, publishFunc); + * + * Mount CUD rest point for collection and documents without GET: + * + * HTTP.publish({collection: myCollection}); + * + */ +HTTP.publish = function httpPublish(options, publishFunc) { + options = _.extend({ + name: null, + auth: null, + collection: null, + defaultFormat: null, + collectionGet: true, + collectionPost: true, + documentGet: true, + documentPut: true, + documentDelete: true + }, options || {}); + + var collection = options.collection; + + // Use provided name or build one + var name = (typeof options.name === "string") ? options.name : defaultAPIPrefix + collection._name; + + // Make sure we have a name + if (typeof name !== "string") { + throw new Error('HTTP.publish expected a collection or name option'); + } + + var defaultFormat = options.defaultFormat; + + // Rig the methods for the CRUD interface + var methods = {}; + + // console.log('HTTP restpoint: ' + name); + + // list and create + methods[name] = {}; + + if (options.collectionGet && publishFunc) { + // Return the published documents + methods[name].get = function(data) { + // Format the scope for the publish method + var publishScope = _publishHTTP.getPublishScope(this); + // Get the publish cursor + var cursor = publishFunc.apply(publishScope, [data]); + + // Check if its a cursor + if (cursor && cursor.fetch) { + // Fetch the data fron cursor + var result = cursor.fetch(); + // Return the data + return _publishHTTP.formatResult(result, this, defaultFormat); + } else { + // We didnt get any + return _publishHTTP.error(200, [], this); + } + }; + } + + if (collection) { + // If we have a collection then add insert method + if (options.collectionPost) { + methods[name].post = function(data) { + var insertMethodHandler = _publishHTTP.getMethodHandler(collection, 'insert'); + // Make sure that _id isset else create a Meteor id + data._id = data._id || Random.id(); + // Create the document + try { + // We should be passed a document in data + insertMethodHandler.apply(this, [data]); + // Return the data + return _publishHTTP.formatResult({ _id: data._id }, this, defaultFormat); + } catch(err) { + // This would be a Meteor.error? + return _publishHTTP.error(err.error, { error: err.message }, this); + } + }; + } + + // We also add the findOne, update and remove methods + methods[name + '/:id'] = {}; + + if (options.documentGet && publishFunc) { + // We have to have a publish method inorder to publish id? The user could + // just write a publish all if needed - better to make this explicit + methods[name + '/:id'].get = function(data) { + // Get the mongoId + var mongoId = this.params.id; + + // We would allways expect a string but it could be empty + if (mongoId !== '') { + + // Format the scope for the publish method + var publishScope = _publishHTTP.getPublishScope(this); + + // Get the publish cursor + var cursor = publishFunc.apply(publishScope, [data]); + + // Result will contain the document if found + var result; + + // Check to see if document is in published cursor + if (cursor) { + cursor.forEach(function(doc) { + if (!result) { + if (doc._id === mongoId) { + result = doc; + } + } + }); + } + + // If the document is found the return + if (result) { + return _publishHTTP.formatResult(result, this, defaultFormat); + } else { + // We do a check to see if the doc id exists + var exists = collection.findOne({ _id: mongoId }); + // If it exists its not published to the user + if (exists) { + // Unauthorized + return _publishHTTP.error(401, { error: 'Unauthorized' }, this); + } else { + // Not found + return _publishHTTP.error(404, { error: 'Document with id ' + mongoId + ' not found' }, this); + } + } + + } else { + return _publishHTTP.error(400, { error: 'Method expected a document id' }, this); + } + }; + } + + if (options.documentPut) { + methods[name + '/:id'].put = function(data) { + // Get the mongoId + var mongoId = this.params.id; + + // We would allways expect a string but it could be empty + if (mongoId !== '') { + + var updateMethodHandler = _publishHTTP.getMethodHandler(collection, 'update'); + // Create the document + try { + // We should be passed a document in data + updateMethodHandler.apply(this, [{ _id: mongoId }, data]); + // Return the data + return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat); + } catch(err) { + // This would be a Meteor.error? + return _publishHTTP.error(err.error, { error: err.message }, this); + } + + } else { + return _publishHTTP.error(400, { error: 'Method expected a document id' }, this); + } + }; + } + + if (options.documentDelete) { + methods[name + '/:id'].delete = function(data) { + // Get the mongoId + var mongoId = this.params.id; + + // We would allways expect a string but it could be empty + if (mongoId !== '') { + + var removeMethodHandler = _publishHTTP.getMethodHandler(collection, 'remove'); + // Create the document + try { + // We should be passed a document in data + removeMethodHandler.apply(this, [{ _id: mongoId }]); + // Return the data + return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat); + } catch(err) { + // This would be a Meteor.error? + return _publishHTTP.error(err.error, { error: err.message }, this); + } + + } else { + return _publishHTTP.error(400, { error: 'Method expected a document id' }, this); + } + }; + } + + } + + // Authenticate with our own auth method: https://github.com/zcfs/Meteor-http-methods#authentication + if (options.auth) { + if (methods[name]) { + methods[name].auth = options.auth; + } + if (methods[name + '/:id']) { + methods[name + '/:id'].auth = options.auth; + } + } + + // Publish the methods + HTTP.methods(methods); + + // Mark these method names as currently published + _publishHTTP.currentlyPublished = _.union(_publishHTTP.currentlyPublished, _.keys(methods)); + +}; // EO Publish + +/** + * @method HTTP.unpublish + * @public + * @param {String|Meteor.Collection} [name] - The method name or collection + * @returns {undefined} + * + * Unpublishes all HTTP methods that were published with the given name or + * for the given collection. Call with no arguments to unpublish all. + */ +HTTP.unpublish = _publishHTTP.unpublish; \ No newline at end of file diff --git a/packages/wekan-cfs-http-publish/http.publish.tests.client.js b/packages/wekan-cfs-http-publish/http.publish.tests.client.js new file mode 100644 index 000000000..185abea23 --- /dev/null +++ b/packages/wekan-cfs-http-publish/http.publish.tests.client.js @@ -0,0 +1,175 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +list = new Meteor.Collection('list'); +console.log('Client url: ' + Meteor.absoluteUrl('api')); + +Tinytest.add('http-publish - client - test environment', function(test) { + test.isTrue(typeof _publishHTTP === 'undefined', 'test environment not initialized _publishHTTP'); + test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP'); + test.isTrue(typeof HTTP.publish !== 'undefined', 'test environment not initialized HTTP.publish'); + test.isTrue(typeof HTTP.unpublish !== 'undefined', 'test environment not initialized HTTP.unpublish'); + test.isTrue(typeof HTTP.publishFormats !== 'undefined', 'test environment not initialized HTTP.publishFormats'); +}); + +Tinytest.addAsync('http-publish - client - clearTest', function (test, onComplete) { + test.isTrue(true); + Meteor.call('clearTest', function(err, result) { + test.isTrue(result); + onComplete(); + }); + test.isTrue(true); +}); + +id = ''; +removedId = ''; + +Tinytest.addAsync('http-publish - client - get list', function (test, onComplete) { + + HTTP.get(Meteor.absoluteUrl('api/list'), function(err, result) { + // Test the length of array result + var len = result.data && result.data.length; + test.isTrue(!!len, 'Result was empty'); + // Get the object + var obj = result.data && result.data[0] || {}; + test.equal(obj.text, 'OK', 'Didnt get the expected result'); + // Set the id for the next test + id = obj._id; + onComplete(); + }); + +}); + +Tinytest.addAsync('http-publish - client - get list from custom prefix', function (test, onComplete) { + + // Now test the one we added with a custom prefix + HTTP.get(Meteor.absoluteUrl('api2/list'), function(err, result) { + // Test the length of array result + var len = result.data && result.data.length; + test.isTrue(!!len, 'Result was empty'); + // Get the object + var obj = result.data && result.data[0] || {}; + test.equal(obj.text, 'OK', 'Didnt get the expected result'); + onComplete(); + }); + +}); + +Tinytest.addAsync('http-publish - client - unmountCustom', function (test, onComplete) { + // Now unmount the methods with custom prefix + test.isTrue(true); + Meteor.call('unmountCustom', function(err, result) { + test.isTrue(result); + onComplete(); + }); + test.isTrue(true); +}); + +Tinytest.addAsync('http-publish - client - custom unmounted', function (test, onComplete) { + + // Now test the one we added with a custom prefix + HTTP.get(Meteor.absoluteUrl('api2/list'), function(err, result) { + test.isTrue(!!err, "Should have received an error since we unmounted the custom rest points"); + onComplete(); + }); + +}); + +Tinytest.addAsync('http-publish - client - put list', function (test, onComplete) { + + test.isTrue(id !== '', 'No id is set?'); + + // Update the data + HTTP.put(Meteor.absoluteUrl('api/list/' + id), { + data: { + $set: { text: 'UPDATED' } + } + }, function(err, result) { + var resultId = result.data && result.data._id; + test.isTrue(resultId !== undefined, 'Didnt get the expected id in result'); + + // Check if data is updated + HTTP.get(Meteor.absoluteUrl('api/list'), function(err, result) { + var len = result.data && result.data.length; + test.isTrue(!!len, 'Result was empty'); + var obj = result.data && result.data[0] || {}; + test.equal(obj.text, 'UPDATED', 'Didnt get the expected result'); + onComplete(); + }); + }); + +}); + +Tinytest.addAsync('http-publish - client - insert/remove list', function (test, onComplete) { + + // Insert a doc + HTTP.post(Meteor.absoluteUrl('api/list'), { + data: { + text: 'INSERTED' + } + }, function(err, result) { + var resultId = result.data && result.data._id; + test.isTrue(resultId !== undefined, 'Didnt get the expected id in result'); + // Delete the doc + HTTP.del(Meteor.absoluteUrl('api/list/' + resultId), function(err, result) { + removedId = result.data && result.data._id; + test.isTrue(removedId !== undefined, 'Didnt get the expected id in result'); + onComplete(); + }); + }); + +}); + +Tinytest.addAsync('http-publish - client - check removed', function (test, onComplete) { + + test.isTrue(removedId !== '', 'No removedId is set?'); + + HTTP.get(Meteor.absoluteUrl('api/list/' + removedId), function(err, result) { + var obj = result.data || {}; + test.isTrue(obj._id === undefined, 'Item was not removed'); + test.isTrue(err.response.statusCode === 404, 'Item was not removed'); + onComplete(); + }); + +}); + +Tinytest.addAsync('http-publish - client - check findOne', function (test, onComplete) { + + test.isTrue(id !== '', 'No id is set?'); + + HTTP.get(Meteor.absoluteUrl('api/list/' + id), function(err, result) { + var obj = result.data || {}; + test.isTrue(obj._id !== undefined, 'expected a document'); + test.isTrue(obj.text === 'UPDATED', 'expected text === UPDATED'); + + onComplete(); + }); + +}); + + + // Check if removedId found + + // Check if id still found + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-http-publish/http.publish.tests.server.js b/packages/wekan-cfs-http-publish/http.publish.tests.server.js new file mode 100644 index 000000000..94ca58a87 --- /dev/null +++ b/packages/wekan-cfs-http-publish/http.publish.tests.server.js @@ -0,0 +1,149 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('http-publish - server - test environment', function(test) { + test.isTrue(typeof _publishHTTP !== 'undefined', 'test environment not initialized _publishHTTP'); + test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP'); + test.isTrue(typeof HTTP.publish !== 'undefined', 'test environment not initialized HTTP.publish'); + test.isTrue(typeof HTTP.unpublish !== 'undefined', 'test environment not initialized HTTP.unpublish'); + test.isTrue(typeof HTTP.publishFormats !== 'undefined', 'test environment not initialized HTTP.publishFormats'); + +}); + +list = new Meteor.Collection('list'); +console.log('Server url: ' + Meteor.absoluteUrl()); + +list.allow({ + insert: function() { return true; }, + update: function() { return true; }, + remove: function() { return true; } +}); + +console.log('Rig publish'); +HTTP.publish({collection: list}, function() { + return list.find(); +}); + +// Test custom prefix, too +HTTP.publish({collection: list, name: '/api2/list'}, function() { + return list.find(); +}); + +Meteor.methods({ + clearTest: function() { + console.log('Client called clearTest'); + // Empty test db + list.remove({}); + + // Insert one text + list.insert({ text: 'OK' }); + + // Count + var count = list.find().count(); + + return !!(count === 1); + }, + unmountCustom: function() { + console.log('Client called unmountCustom'); + _publishHTTP.unpublish('/api2/list'); + return true; + } +}); + + +Tinytest.add('http-publish - server - getMethodHandler', function(test) { + + try { + var methodHandler = _publishHTTP.getMethodHandler(list, 'insert'); + + test.isTrue(typeof methodHandler === 'function', 'expected getMethodHandler to return a function'); + + } catch(err) { + test.fail(err.message); + } + +}); + + +Tinytest.add('http-publish - server - formatHandlers', function(test) { + + test.isTrue(typeof _publishHTTP.formatHandlers.json === 'function', 'Cant find formatHandler for json'); + + var testScope = { + code: 0, + setContentType: function(code) { + this.code = code; + } + }; + var resultFormatHandler = _publishHTTP.formatHandlers.json.apply(testScope, [{test:'ok'}]); + + test.equal(testScope.code, 'application/json', 'json formatHandler have not set setContentType'); + + test.equal(resultFormatHandler, '{"test":"ok"}', 'json formatHandler returned a bad result'); + +}); + +Tinytest.add('http-publish - server - getPublishScope', function(test) { + + var oldScope = { + userId: '1', + params: '2', + query: '3', + oldStuff: 'hmmm' + }; + + var newScope = _publishHTTP.getPublishScope(oldScope); + + test.isUndefined(newScope.oldStuff, 'This oldStuff should not be in the new scope'); + + test.equal(newScope.userId, '1', 'userId not set in the new scope'); + test.equal(newScope.params, '2', 'params not set in the new scope'); + test.equal(newScope.query, '3', 'query not set in the new scope'); + +}); + +Tinytest.add('http-publish - server - formatResult', function(test) { + + var oldScope = { + statusCode: 200, + userId: '1', + params: '2', + query: '3', + oldStuff: 'hmmm', + setStatusCode: function(code) { + this.statusCode = code; + }, + code: 0, + setContentType: function(code) { + this.code = code; + } + }; + + var result = _publishHTTP.formatResult({test: 'ok'}, oldScope); + + test.equal(oldScope.code, 'application/json', 'json formatHandler have not set setContentType'); + + test.equal(result, '{"test":"ok"}', 'json formatHandler returned a bad result'); + +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-http-publish/internal.api.md b/packages/wekan-cfs-http-publish/internal.api.md new file mode 100644 index 000000000..ce31a3e53 --- /dev/null +++ b/packages/wekan-cfs-http-publish/internal.api.md @@ -0,0 +1,330 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["http.publish.server.api.js"](http.publish.server.api.js) Where: {server}__ + +*** + +### _publishHTTP {any}  Server ### + +``` +GET /note +GET /note/:id +POST /note +PUT /note/:id +DELETE /note/:id +``` + +> ```_publishHTTP = { ...``` [http.publish.server.api.js:20](http.publish.server.api.js#L20) + + + +- +Could be cool if we could serve some api doc or even an api script +user could do and be served +a client-side javascript api? +Eg. +HTTP.api.note.create(); +HTTP.api.login(username, password); +HTTP.api.logout +- + +### *_publishhttp*.getPublishScope(scope)  Server ### + +*This method is private* +*This method __getPublishScope__ is defined in `_publishHTTP`* + +__Arguments__ + +* __scope__ *{Object}* + +__Returns__ *{httpPublishGetPublishScope.publishScope}* + + +Creates a nice scope for the publish method + +> ```_publishHTTP.getPublishScope = function httpPublishGetPublishScope(scope) { ...``` [http.publish.server.api.js:35](http.publish.server.api.js#L35) + + +- + +### *_publishhttpformathandlers*.json(result)  Server ### + +*This method is private* +*This method __json__ is defined in `_publishHTTP.formatHandlers`* + +__Arguments__ + +* __result__ *{Object}* + + The result object + + +__Returns__ *{String}* +JSON + + +Formats the output into JSON and sets the appropriate content type on `this` + +> ```_publishHTTP.formatHandlers.json = function httpPublishJSONFormatHandler(result) { ...``` [http.publish.server.api.js:56](http.publish.server.api.js#L56) + + +- + +### *_publishhttp*.formatResult(result, scope, [defaultFormat])  Server ### + +*This method is private* +*This method __formatResult__ is defined in `_publishHTTP`* + +__Arguments__ + +* __result__ *{Object}* + + The result object + +* __scope__ *{Object}* +* __defaultFormat__ *{String}* (Optional, Default = 'json') + + Default format to use if format is not in query string. + + +__Returns__ *{Any}* +The formatted result + + +Formats the result into the format selected by querystring eg. "&format=json" + +> ```_publishHTTP.formatResult = function httpPublishFormatResult(result, scope, defaultFormat) { ...``` [http.publish.server.api.js:73](http.publish.server.api.js#L73) + + +- + +### *_publishhttp*.error(statusCode, message, scope)  Server ### + +*This method is private* +*This method __error__ is defined in `_publishHTTP`* + +__Arguments__ + +* __statusCode__ *{String}* + + The status code + +* __message__ *{String}* + + The message + +* __scope__ *{Object}* + +__Returns__ *{Any}* +The formatted result + + +Responds with error message in the expected format + +> ```_publishHTTP.error = function httpPublishError(statusCode, message, scope) { ...``` [http.publish.server.api.js:114](http.publish.server.api.js#L114) + + +- + +### *_publishhttp*.getMethodHandler(collection, methodName)  Server ### + +*This method is private* +*This method __getMethodHandler__ is defined in `_publishHTTP`* + +__Arguments__ + +* __collection__ *{[Meteor.Collection](#Meteor.Collection)}* + + The Meteor.Collection instance + +* __methodName__ *{String}* + + The method name + + +__Returns__ *{Function}* +The server method + + +Returns the DDP connection handler, already setup and secured + +> ```_publishHTTP.getMethodHandler = function httpPublishGetMethodHandler(collection, methodName) { ...``` [http.publish.server.api.js:129](http.publish.server.api.js#L129) + + +- + +### *_publishhttp*.unpublishList(names)  Server ### + +*This method is private* +*This method __unpublishList__ is defined in `_publishHTTP`* + +__Arguments__ + +* __names__ *{Array}* + + List of method names to unpublish + + +__Returns__ *{undefined}* + + +Unpublishes all HTTP methods that have names matching the given list. + +> ```_publishHTTP.unpublishList = function httpPublishUnpublishList(names) { ...``` [http.publish.server.api.js:149](http.publish.server.api.js#L149) + + +- + +### *_publishhttp*.unpublish([name])  Server ### + +*This method is private* +*This method __unpublish__ is defined in `_publishHTTP`* + +__Arguments__ + +* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional) + + The method name or collection + + +__Returns__ *{undefined}* + + +Unpublishes all HTTP methods that were published with the given name or +for the given collection. Call with no arguments to unpublish all. + +> ```_publishHTTP.unpublish = function httpPublishUnpublish(``` [http.publish.server.api.js:177](http.publish.server.api.js#L177) + + +- + +### *http*.publishFormats(newHandlers)  Server ### + +*This method __publishFormats__ is defined in `HTTP`* + +__Arguments__ + +* __newHandlers__ *{Object}* + +__Returns__ *{undefined}* + + +Add publish formats. Example: +```js +HTTP.publishFormats({ +json: function(inputObject) { +// Set the method scope content type to json +this.setContentType('application/json'); +// Return EJSON string +return EJSON.stringify(inputObject); +} +}); +``` + +> ```HTTP.publishFormats = function httpPublishFormats(newHandlers) { ...``` [http.publish.server.api.js:215](http.publish.server.api.js#L215) + + +- + +### *http*.publish(options, [name], [collection], [publishFunc])  Server ### + +*This method __publish__ is defined in `HTTP`* + +__Arguments__ + +* __options__ *{Object}* + * __defaultFormat__ *{String}* (Optional, Default = 'json') + + Format to use for responses when `format` is not found in the query string. + + * __collectionGet__ *{String}* (Optional, Default = true) + + Add GET restpoint for collection? Requires a publish function. + + * __collectionPost__ *{String}* (Optional, Default = true) + + Add POST restpoint for adding documents to the collection? + + * __documentGet__ *{String}* (Optional, Default = true) + + Add GET restpoint for documents in collection? Requires a publish function. + + * __documentPut__ *{String}* (Optional, Default = true) + + Add PUT restpoint for updating a document in the collection? + + * __documentDelete__ *{String}* (Optional, Default = true) + + Add DELETE restpoint for deleting a document in the collection? + +* __name__ *{String}* (Optional) + + Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default. + +* __collection__ *{[Meteor.Collection](#Meteor.Collection)}* (Optional) + + Meteor.Collection instance. Required for all restpoints except collectionGet + +* __publishFunc__ *{Function}* (Optional) + + A publish function. Required to mount GET restpoints. + + +__Returns__ *{undefined}* + +__TODO__ +``` +* this should use options argument instead of optional args +``` + + +Publishes one or more restpoints, mounted on "name" ("/api/collectionName/" +by default). The GET restpoints are subscribed to the document set (cursor) +returned by the publish function you supply. The other restpoints forward +requests to Meteor's built-in DDP methods (insert, update, remove), meaning +that full allow/deny security is automatic. + +__Usage:__ + +Publish only: + +HTTP.publish({name: 'mypublish'}, publishFunc); + +Publish and mount crud rest point for collection /api/myCollection: + +HTTP.publish({collection: myCollection}, publishFunc); + +Mount CUD rest point for collection and documents without GET: + +HTTP.publish({collection: myCollection}); + + +> ```HTTP.publish = function httpPublish(options, publishFunc) { ...``` [http.publish.server.api.js:256](http.publish.server.api.js#L256) + + +- + +### *http*.unpublish([name])  Server ### + +*This method __unpublish__ is defined in `HTTP`* + +__Arguments__ + +* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional) + + The method name or collection + + +__Returns__ *{undefined}* + + +Unpublishes all HTTP methods that were published with the given name or +for the given collection. Call with no arguments to unpublish all. + +> ```HTTP.unpublish = _publishHTTP.unpublish;``` [http.publish.server.api.js:453](http.publish.server.api.js#L453) + + diff --git a/packages/wekan-cfs-http-publish/package.js b/packages/wekan-cfs-http-publish/package.js new file mode 100644 index 000000000..7a16373cc --- /dev/null +++ b/packages/wekan-cfs-http-publish/package.js @@ -0,0 +1,34 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-http-publish.git', + name: 'wekan-cfs-http-publish', + version: '0.0.13', + summary: 'Adds HTTP.publish and HTTP.unpublish RESTful' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use(['webapp', 'underscore', 'ejson', 'random'], 'server'); + + api.use('wekan-cfs-http-methods@0.0.27'); + + api.imply && api.imply('wekan-cfs-http-methods'); + + api.export && api.export('_publishHTTP', { testOnly: true }); + + api.addFiles('http.publish.client.api.js', 'client'); + api.addFiles('http.publish.server.api.js', 'server'); + +}); + +Package.onTest(function (api) { + api.use('wekan-cfs-http-publish', ['client', 'server']); + api.use('test-helpers', ['client', 'server']); + api.use('http', 'client'); + + api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', + 'random', 'deps']); + + api.addFiles('http.publish.tests.server.js', 'server'); + api.addFiles('http.publish.tests.client.js', 'client'); +}); diff --git a/packages/wekan-cfs-http-publish/packages/.gitignore b/packages/wekan-cfs-http-publish/packages/.gitignore new file mode 100644 index 000000000..3683f43ae --- /dev/null +++ b/packages/wekan-cfs-http-publish/packages/.gitignore @@ -0,0 +1,2 @@ +/http-publish +/http-methods diff --git a/packages/wekan-cfs-power-queue/.editorconfig b/packages/wekan-cfs-power-queue/.editorconfig new file mode 100644 index 000000000..a2cc1c1fe --- /dev/null +++ b/packages/wekan-cfs-power-queue/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +# Meteor adapted EditorConfig, http://EditorConfig.org +# By RaiX 2013 + +root = true + +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto +# curly_bracket_next_line = true \ No newline at end of file diff --git a/packages/wekan-cfs-power-queue/.gitignore b/packages/wekan-cfs-power-queue/.gitignore new file mode 100644 index 000000000..dc163f991 --- /dev/null +++ b/packages/wekan-cfs-power-queue/.gitignore @@ -0,0 +1,3 @@ +/versions.json +/nbproject/private/ +.build* diff --git a/packages/wekan-cfs-power-queue/.jshintrc b/packages/wekan-cfs-power-queue/.jshintrc new file mode 100644 index 000000000..2bc2606c4 --- /dev/null +++ b/packages/wekan-cfs-power-queue/.jshintrc @@ -0,0 +1,114 @@ +//.jshintrc +{ + // JSHint Meteor Configuration File + // Match the Meteor Style Guide + // + // By @raix with contributions from @aldeed and @awatson1978 + // Source https://github.com/raix/Meteor-jshintrc + // + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : true, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "trailing" : true, // true: Prohibit trailing whitespaces + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : 80, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + //"meteor" : false, // Meteor.js + + // Legacy + "nomen" : false, // true: Prohibit dangling `_` in variables + "onevar" : false, // true: Allow only one `var` statement per function + "passfail" : false, // true: Stop on first error + "white" : false, // true: Check against strict whitespace and indentation rules + + // Custom Globals + "predef" : [ + "Meteor", + "Accounts", + "Session", + "Template", + "check", + "Match", + "Deps", + "EJSON", + "Email", + "Package", + "Tinytest", + "Npm", + "Assets", + "Packages", + "process", + "GroundDB", + "_gDB", + "LocalCollection", + "_", + "Random" + ] // additional predefined global variables +} diff --git a/packages/wekan-cfs-power-queue/.travis.yml b/packages/wekan-cfs-power-queue/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-power-queue/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-power-queue/LICENSE.md b/packages/wekan-cfs-power-queue/LICENSE.md new file mode 100644 index 000000000..2a5c5339e --- /dev/null +++ b/packages/wekan-cfs-power-queue/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-power-queue/README.md b/packages/wekan-cfs-power-queue/README.md new file mode 100644 index 000000000..7b889a7c0 --- /dev/null +++ b/packages/wekan-cfs-power-queue/README.md @@ -0,0 +1,136 @@ +wekan-cfs-power-queue [![Build Status](https://travis-ci.org/CollectionFS/Meteor-powerqueue.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-powerqueue) +========= + +~~Looking for maintainers - please reach out!~~ +This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible. + +**If you're looking for an alternative, we highly recommend [Meteor-Files](https://github.com/VeliovGroup/Meteor-Files) by [VeliovGroup](https://github.com/VeliovGroup)** + +--- + +PowerQueue is a native Meteor package for memory-backed job queue processing. Features include: +* async tasks +* throttling resource usage +* retrying failed tasks +* managing sub-queues +* powered by Meteor's reactive sugar +* etc. + +PowerQueue can use one of two [spinal-queue](https://github.com/zcfs/Meteor-power-queue/blob/master/spinal-queue.spec.md) packages, [ReactiveList](https://github.com/zcfs/Meteor-reactive-list) or [MicroQueue](https://github.com/zcfs/Meteor-micro-queue). + +## Demos + +**Check out the cool [live queue demo](http://power-queue-test.meteor.com) and [live sub queue example](http://power-queue-sub-test.meteor.com).** + +Source code for both can be found in the two branches of the [power-queue-example repo](https://github.com/zcfs/power-queue-example). + + +Kind regards, +Eric(@aldeed) and Morten(@raix) + +Happy coding! + +# API +All getters and setters are reactive. + +[API Documentation](api.md) + +## Helpers / Getters / Setters: +* PowerQueue.length - Number of tasks in queue +* PowerQueue.progress - Current progress in percent +* PowerQueue.usage - Current load in percent +* PowerQueue.total - Sum of tasks to run in current queue +* PowerQueue.isPaused - True if queue is paused +* PowerQueue.isHalted - True if queue is paused or stopped +* PowerQueue.processing - Number of tasks being processed +* PowerQueue.errors - Failures where task is passed to the errorHandler +* PowerQueue.failures - Number of failures in current queue +* PowerQueue.isRunning - True if queue is active +* PowerQueue.maxProcessing - Getter + Setter for max tasks to run in parallel +* PowerQueue.autostart - Getter + Setter for autostart flag - Allow add task to start the queue +* PowerQueue.maxFailures - Max allowed retries for failing tasks before marked as an error +* options.queue - Use custom micro-queue compatible queue +* options.onEnded - Called when queue has ended +* options.onRelease(remainingTasks) - Called when queue has ended or paused +* options.onAutostart - Called when queue was autostarted + +## Methods +* PowerQueue.add(data) - Add a task to queue +* PowerQueue.run() - Start the queue +* PowerQueue.pause() - Pause the queue +* PowerQueue.resume() - Resume the queue if paused +* PowerQueue.reset() - Reset the queue +* PowerQueue.taskHandler(data, next, failures) - Default task handler, where data is a `function(done)`, can be overwritten +* PowerQueue.errorHandler(data, addTask, failures) - Default error handler, can be overwritten + +# Example 1 +```js + var queue = new PowerQueue({ + isPaused: true + }); + + queue.add(function(done) { + console.log('task 1'); + done(); + }); + queue.add(function(done) { + console.log('task 2'); + done(); + }); + queue.add(function(done) { + console.log('task 3'); + done(); + }); + + console.log('Ready to run queue'); + queue.run(); +``` + +# Example 2 + +This is a very rough example of how to make custom task handling. + +```js + + queue.errorHandler = function(data, addTask) { + // This error handler lets the task drop, but we could use addTask to + // Put the task into the queue again + tasks.update({ _id: data.id }, { $set: { status: 'error'} }); + }; + + queue.taskHandler = function(data, next) { + + // The task is now processed... + tasks.update({ _id: data.id }, { $set: { status: 'processing'} }); + + Meteor.setTimeout(function() { + if (Math.random() > 0.5) { + // We random fail the task + tasks.update({ _id: data.id }, { $set: { status: 'failed'} }); + // Returning error to next + next('Error: Fail task'); + } else { + // We are done! + tasks.update({ _id: data.id }, { $set: { status: 'done'} }); + // Trigger next task + next(); + } + // This async task duration is between 500 - 1000ms + }, Math.round(500 + 500 * Math.random())); + }; + + // Add the task: + var taskId = 0; + queue.add({ id: tasks.insert({ status: 'added', index: ++taskId }) }); +``` + +# Contribute + +Here's the [complete API documentation](internal.api.md), including private methods. + +To update the docs, run `npm install docmeteor` then `docmeteor`. + + +## TODO / Wishlist + +* scheduling jobs to run in the future, like [meteor-queue](https://github.com/artwells/meteor-queue#features) - see [issue #15](https://github.com/zcfs/Meteor-power-queue/issues/15) diff --git a/packages/wekan-cfs-power-queue/api.md b/packages/wekan-cfs-power-queue/api.md new file mode 100644 index 000000000..435610d8f --- /dev/null +++ b/packages/wekan-cfs-power-queue/api.md @@ -0,0 +1,420 @@ + +#### new PowerQueue([options])  Anywhere #### +``` +Creates an instance of a power queue +[Check out demo](http://power-queue-test.meteor.com/) +``` +- + +__Arguments__ + +* __options__ *{object}* (Optional) +Settings + - __filo__ *{boolean}* (Default = false) +Make it a first in last out queue + - __isPaused__ *{boolean}* (Default = false) +Set queue paused + - __autostart__ *{boolean}* (Default = true) +May adding a task start the queue + - __name__ *{string}* (Default = "Queue") +Name of the queue + - __maxProcessing__ *{number}* (Default = 1) +Limit of simultanous running tasks + - __maxFailures__ *{number}* (Default = 5) +Limit retries of failed tasks, if 0 or below we allow infinite failures + - __jumpOnFailure__ *{number}* (Default = true) +Jump to next task and retry failed task later + - __debug__ *{boolean}* (Default = false) +Log verbose messages to the console + - __reactive__ *{boolean}* (Default = true) +Set whether or not this queue should be reactive + - __spinalQueue__ *{[SpinalQueue](spinal-queue.spec.md)}* (Optional) +Set spinal queue uses pr. default `MicroQueue` or `ReactiveList` if added to the project + +- + + + +> ```PowerQueue = function(options) { ...``` [power-queue.js:27](power-queue.js#L27) + +- + +#### *powerqueue*.onEnded  Anywhere #### +- +*This callback __onEnded__ is defined in `PowerQueue`* +Is called when queue is ended + +> ```self.onEnded = options && options.onEnded || function() { ...``` [power-queue.js:103](power-queue.js#L103) + +- + +#### *powerqueue*.onRelease  Anywhere #### +- +*This callback __onRelease__ is defined in `PowerQueue`* +Is called when queue is released + +> ```self.onRelease = options && options.onRelease || function() { ...``` [power-queue.js:110](power-queue.js#L110) + +- + +#### *powerqueue*.onAutostart  Anywhere #### +- +*This callback __onAutostart__ is defined in `PowerQueue`* +Is called when queue is auto started + +> ```self.onAutostart = options && options.onAutostart || function() { ...``` [power-queue.js:115](power-queue.js#L115) + +- + +#### *powerqueue*.total()  Anywhere #### +- +*This method __total__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of tasks added to this queue + +> ```self.total = self._maxLength.get;``` [power-queue.js:123](power-queue.js#L123) + +- + +#### *powerqueue*.isPaused()  Anywhere #### +- +*This method __isPaused__ is defined in `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +Status of the paused state of the queue + +> ```self.isPaused = self._paused.get;``` [power-queue.js:129](power-queue.js#L129) + +- + +#### *powerqueue*.processing()  Anywhere #### +- +*This method __processing__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +Number of tasks currently being processed + +> ```self.processing = self._isProcessing.get;``` [power-queue.js:135](power-queue.js#L135) + +- + +#### *powerqueue*.errors()  Anywhere #### +- +*This method __errors__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of errors +Errors are triggered when [maxFailures](PowerQueue.maxFailures) are exeeded + +> ```self.errors = self._errors.get;``` [power-queue.js:142](power-queue.js#L142) + +- + +#### *powerqueue*.failures()  Anywhere #### +- +*This method __failures__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of failed tasks + +> ```self.failures = self._failures.get;``` [power-queue.js:148](power-queue.js#L148) + +- + +#### *powerqueue*.isRunning()  Anywhere #### +- +*This method __isRunning__ is defined in `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +True if the queue is running +> NOTE: The task can be paused but marked as running + +> ```self.isRunning = self._running.get;``` [power-queue.js:155](power-queue.js#L155) + +- + +#### *powerqueue*.maxProcessing([max])  Anywhere #### +- +*This method __maxProcessing__ is defined in `PowerQueue`* + +__Arguments__ + +* __max__ *{number}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{number}* __(is reactive)__ +Maximum number of simultaneous processing tasks + +Example: +```js + foo.maxProcessing(); // Works as a getter and returns the current value + foo.maxProcessing(20); // This sets the value to 20 +``` + +> ```self.maxProcessing = self._maxProcessing.getset;``` [power-queue.js:168](power-queue.js#L168) + +- + +#### *powerqueue*.autostart([autorun])  Anywhere #### +- +*This method __autostart__ is defined in `PowerQueue`* + +__Arguments__ + +* __autorun__ *{boolean}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{boolean}* __(is reactive)__ +If adding a task may trigger the queue to start + +Example: +```js + foo.autostart(); // Works as a getter and returns the current value + foo.autostart(true); // This sets the value to true +``` + +> ```self.autostart = self._autostart.getset;``` [power-queue.js:189](power-queue.js#L189) + +- + +#### *powerqueue*.maxFailures([max])  Anywhere #### +- +*This method __maxFailures__ is defined in `PowerQueue`* + +__Arguments__ + +* __max__ *{number}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{number}* __(is reactive)__ +The maximum for failures pr. task before triggering an error + +Example: +```js + foo.maxFailures(); // Works as a getter and returns the current value + foo.maxFailures(10); // This sets the value to 10 +``` + +> ```self.maxFailures = self._maxFailures.getset;``` [power-queue.js:202](power-queue.js#L202) + +- + +#### *powerqueue*.processList()  Anywhere #### +- +*This method __processList__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{array}* __(is reactive)__ +List of tasks currently being processed + +> ```PowerQueue.prototype.processingList = function() { ...``` [power-queue.js:209](power-queue.js#L209) + +- + +#### *powerqueue*.isHalted()  Anywhere #### +- +*This method __isHalted__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +True if the queue is not running or paused + +> ```PowerQueue.prototype.isHalted = function() { ...``` [power-queue.js:218](power-queue.js#L218) + +- + +#### *powerqueue*.length()  Anywhere #### +- +*This method __length__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +Number of tasks left in queue to be processed + +> ```PowerQueue.prototype.length = function() { ...``` [power-queue.js:227](power-queue.js#L227) + +- + +#### *powerqueue*.progress()  Anywhere #### +- +*This method __progress__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +0 .. 100 % Indicates the status of the queue + +> ```PowerQueue.prototype.progress = function() { ...``` [power-queue.js:236](power-queue.js#L236) + +- + +#### *powerqueue*.usage()  Anywhere #### +- +*This method __usage__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +0 .. 100 % Indicates ressource usage of the queue + +> ```PowerQueue.prototype.usage = function() { ...``` [power-queue.js:249](power-queue.js#L249) + +- + +#### *powerqueue*.reset()  Anywhere #### +- +*This method __reset__ is defined in `prototype` of `PowerQueue`* +Calling this will: +* stop the queue +* paused to false +* Discart all queue data + +> NOTE: At the moment if the queue has processing tasks they can change +> the `errors` and `failures` counters. This could change in the future or +> be prevented by creating a whole new instance of the `PowerQueue` + +> ```PowerQueue.prototype.reset = function() { ...``` [power-queue.js:264](power-queue.js#L264) + +- + +#### *powerqueue*.add(data, [failures])  Anywhere #### +- +*This method __add__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +The task to be handled +* __failures__ *{number}* (Optional) +Internally used to Pass on number of failures. + +- + +> ```PowerQueue.prototype.add = function(data, failures, id) { ...``` [power-queue.js:316](power-queue.js#L316) + +- + +#### *powerqueue*.next([err])  Anywhere #### +- +*This method __next__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __err__ *{string}* (Optional) +Error message if task failed + +- +> * Can pass in `null` to start the queue +> * Passing in a string to `next` will trigger a failure +> * Passing nothing will simply let the next task run +`next` is handed into the [taskHandler](PowerQueue.taskHandler) as a +callback to mark an error or end of current task + +> ```PowerQueue.prototype.next = function(err) { ...``` [power-queue.js:394](power-queue.js#L394) + +- + +#### *powerqueue*.queueTaskHandler()  Anywhere #### +- +*This method __queueTaskHandler__ is defined in `prototype` of `PowerQueue`* +This method handles tasks that are sub queues + +> ```PowerQueue.prototype.queueTaskHandler = function(subQueue, next, failures) { ...``` [power-queue.js:555](power-queue.js#L555) + +- + +#### *powerqueue*.taskHandler  Anywhere #### +- +*This callback __taskHandler__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +This can be data or functions +* __next__ *{function}* +Function `next` call this to end task +* __failures__ *{number}* +Number of failures on this task + +- + +Default task handler expects functions as data: +```js + self.taskHandler = function(data, next, failures) { + // This default task handler expects invocation to be a function to run + if (typeof data !== 'function') { + throw new Error('Default task handler expects a function'); + } + try { + // Have the function call next + data(next, failures); + } catch(err) { + // Throw to fail this task + next(err); + } + }; +``` + +> ```PowerQueue.prototype.taskHandler = function(data, next, failures) { ...``` [power-queue.js:601](power-queue.js#L601) + +- + +#### *powerqueue*.errorHandler  Anywhere #### +- +*This callback __errorHandler__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +This can be data or functions +* __addTask__ *{function}* +Use this function to insert the data into the queue again +* __failures__ *{number}* +Number of failures on this task + +- + +The default callback: +```js + var foo = new PowerQueue(); + + // Overwrite the default action + foo.errorHandler = function(data, addTask, failures) { + // This could be overwritten the data contains the task data and addTask + // is a helper for adding the task to the queue + // try again: addTask(data); + // console.log('Terminate at ' + failures + ' failures'); + }; +``` + +> ```PowerQueue.prototype.errorHandler = function(data, addTask, failures) { ...``` [power-queue.js:634](power-queue.js#L634) + +- + +#### *powerqueue*.pause()  Anywhere #### +- +*This method __pause__ is defined in `prototype` of `PowerQueue`* + +> ```PowerQueue.prototype.pause = function() { ...``` [power-queue.js:645](power-queue.js#L645) + +- + +#### *powerqueue*.resume()  Anywhere #### +- +*This method __resume__ is defined in `prototype` of `PowerQueue`* + +> This will not start a stopped queue + +> ```PowerQueue.prototype.resume = function() { ...``` [power-queue.js:665](power-queue.js#L665) + +- + +#### *powerqueue*.run()  Anywhere #### +- +*This method __run__ is defined in `prototype` of `PowerQueue`* +> Using this command will resume a paused queue and will +> start a stopped queue. + +> ```PowerQueue.prototype.run = function() { ...``` [power-queue.js:677](power-queue.js#L677) + +- diff --git a/packages/wekan-cfs-power-queue/internal.api.md b/packages/wekan-cfs-power-queue/internal.api.md new file mode 100644 index 000000000..797f1f454 --- /dev/null +++ b/packages/wekan-cfs-power-queue/internal.api.md @@ -0,0 +1,535 @@ +> File: ["power-queue.js"](power-queue.js) +> Where: {client|server} + +- + +#### new PowerQueue([options])  Anywhere #### +``` +Creates an instance of a power queue +[Check out demo](http://power-queue-test.meteor.com/) +``` +- + +__Arguments__ + +* __options__ *{object}* (Optional) +Settings + - __filo__ *{boolean}* (Default = false) +Make it a first in last out queue + - __isPaused__ *{boolean}* (Default = false) +Set queue paused + - __autostart__ *{boolean}* (Default = true) +May adding a task start the queue + - __name__ *{string}* (Default = "Queue") +Name of the queue + - __maxProcessing__ *{number}* (Default = 1) +Limit of simultanous running tasks + - __maxFailures__ *{number}* (Default = 5) +Limit retries of failed tasks, if 0 or below we allow infinite failures + - __jumpOnFailure__ *{number}* (Default = true) +Jump to next task and retry failed task later + - __debug__ *{boolean}* (Default = false) +Log verbose messages to the console + - __reactive__ *{boolean}* (Default = true) +Set whether or not this queue should be reactive + - __spinalQueue__ *{[SpinalQueue](spinal-queue.spec.md)}* (Optional) +Set spinal queue uses pr. default `MicroQueue` or `ReactiveList` if added to the project + +- + + + +> ```PowerQueue = function(options) { ...``` [power-queue.js:27](power-queue.js#L27) + +- + +#### *powerqueue*.onEnded  Anywhere #### +- +*This callback __onEnded__ is defined in `PowerQueue`* +Is called when queue is ended + +> ```self.onEnded = options && options.onEnded || function() { ...``` [power-queue.js:103](power-queue.js#L103) + +- + +#### *powerqueue*.onRelease  Anywhere #### +- +*This callback __onRelease__ is defined in `PowerQueue`* +Is called when queue is released + +> ```self.onRelease = options && options.onRelease || function() { ...``` [power-queue.js:110](power-queue.js#L110) + +- + +#### *powerqueue*.onAutostart  Anywhere #### +- +*This callback __onAutostart__ is defined in `PowerQueue`* +Is called when queue is auto started + +> ```self.onAutostart = options && options.onAutostart || function() { ...``` [power-queue.js:115](power-queue.js#L115) + +- + +#### *powerqueue*.total()  Anywhere #### +- +*This method __total__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of tasks added to this queue + +> ```self.total = self._maxLength.get;``` [power-queue.js:123](power-queue.js#L123) + +- + +#### *powerqueue*.isPaused()  Anywhere #### +- +*This method __isPaused__ is defined in `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +Status of the paused state of the queue + +> ```self.isPaused = self._paused.get;``` [power-queue.js:129](power-queue.js#L129) + +- + +#### *powerqueue*.processing()  Anywhere #### +- +*This method __processing__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +Number of tasks currently being processed + +> ```self.processing = self._isProcessing.get;``` [power-queue.js:135](power-queue.js#L135) + +- + +#### *powerqueue*.errors()  Anywhere #### +- +*This method __errors__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of errors +Errors are triggered when [maxFailures](PowerQueue.maxFailures) are exeeded + +> ```self.errors = self._errors.get;``` [power-queue.js:142](power-queue.js#L142) + +- + +#### *powerqueue*.failures()  Anywhere #### +- +*This method __failures__ is defined in `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +The total number of failed tasks + +> ```self.failures = self._failures.get;``` [power-queue.js:148](power-queue.js#L148) + +- + +#### *powerqueue*.isRunning()  Anywhere #### +- +*This method __isRunning__ is defined in `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +True if the queue is running +> NOTE: The task can be paused but marked as running + +> ```self.isRunning = self._running.get;``` [power-queue.js:155](power-queue.js#L155) + +- + +#### *powerqueue*.maxProcessing([max])  Anywhere #### +- +*This method __maxProcessing__ is defined in `PowerQueue`* + +__Arguments__ + +* __max__ *{number}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{number}* __(is reactive)__ +Maximum number of simultaneous processing tasks + +Example: +```js + foo.maxProcessing(); // Works as a getter and returns the current value + foo.maxProcessing(20); // This sets the value to 20 +``` + +> ```self.maxProcessing = self._maxProcessing.getset;``` [power-queue.js:168](power-queue.js#L168) + +- + +#### *powerqueue*.autostart([autorun])  Anywhere #### +- +*This method __autostart__ is defined in `PowerQueue`* + +__Arguments__ + +* __autorun__ *{boolean}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{boolean}* __(is reactive)__ +If adding a task may trigger the queue to start + +Example: +```js + foo.autostart(); // Works as a getter and returns the current value + foo.autostart(true); // This sets the value to true +``` + +> ```self.autostart = self._autostart.getset;``` [power-queue.js:189](power-queue.js#L189) + +- + +#### *powerqueue*.maxFailures([max])  Anywhere #### +- +*This method __maxFailures__ is defined in `PowerQueue`* + +__Arguments__ + +* __max__ *{number}* (Optional) +If not used this function works as a getter + +- + +__Returns__ *{number}* __(is reactive)__ +The maximum for failures pr. task before triggering an error + +Example: +```js + foo.maxFailures(); // Works as a getter and returns the current value + foo.maxFailures(10); // This sets the value to 10 +``` + +> ```self.maxFailures = self._maxFailures.getset;``` [power-queue.js:202](power-queue.js#L202) + +- + +#### *powerqueue*.processList()  Anywhere #### +- +*This method __processList__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{array}* __(is reactive)__ +List of tasks currently being processed + +> ```PowerQueue.prototype.processingList = function() { ...``` [power-queue.js:209](power-queue.js#L209) + +- + +#### *powerqueue*.isHalted()  Anywhere #### +- +*This method __isHalted__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{boolean}* __(is reactive)__ +True if the queue is not running or paused + +> ```PowerQueue.prototype.isHalted = function() { ...``` [power-queue.js:218](power-queue.js#L218) + +- + +#### *powerqueue*.length()  Anywhere #### +- +*This method __length__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +Number of tasks left in queue to be processed + +> ```PowerQueue.prototype.length = function() { ...``` [power-queue.js:227](power-queue.js#L227) + +- + +#### *powerqueue*.progress()  Anywhere #### +- +*This method __progress__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +0 .. 100 % Indicates the status of the queue + +> ```PowerQueue.prototype.progress = function() { ...``` [power-queue.js:236](power-queue.js#L236) + +- + +#### *powerqueue*.usage()  Anywhere #### +- +*This method __usage__ is defined in `prototype` of `PowerQueue`* + +__Returns__ *{number}* __(is reactive)__ +0 .. 100 % Indicates ressource usage of the queue + +> ```PowerQueue.prototype.usage = function() { ...``` [power-queue.js:249](power-queue.js#L249) + +- + +#### *powerqueue*.reset()  Anywhere #### +- +*This method __reset__ is defined in `prototype` of `PowerQueue`* +Calling this will: +* stop the queue +* paused to false +* Discart all queue data + +> NOTE: At the moment if the queue has processing tasks they can change +> the `errors` and `failures` counters. This could change in the future or +> be prevented by creating a whole new instance of the `PowerQueue` + +> ```PowerQueue.prototype.reset = function() { ...``` [power-queue.js:264](power-queue.js#L264) + +- + +#### *powerqueue*._autoStartTasks()  Anywhere #### +- +*This method is private* +*This method ___autoStartTasks__ is defined in `PowerQueue`* + +This method defines the autostart algorithm that allows add task to trigger +a start of the queue if queue is not paused. + +> ```PowerQueue.prototype._autoStartTasks = function() { ...``` [power-queue.js:289](power-queue.js#L289) + +- + +#### *powerqueue*.add(data, [failures])  Anywhere #### +- +*This method __add__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +The task to be handled +* __failures__ *{number}* (Optional) +Internally used to Pass on number of failures. + +- + +> ```PowerQueue.prototype.add = function(data, failures, id) { ...``` [power-queue.js:316](power-queue.js#L316) + +- + +#### *powerqueue*.updateThrottleUp()  Anywhere #### +- +*This method is private* +*This method __updateThrottleUp__ is defined in `prototype` of `PowerQueue`* + +Calling this method will update the throttle on the queue adding tasks. + +> Note: Currently we only support the PowerQueue - but we could support +> a more general interface for pauseable tasks or other usecases. + +> ```PowerQueue.prototype.updateThrottleUp = function() { ...``` [power-queue.js:342](power-queue.js#L342) + +- + +#### *powerqueue*.updateThrottleDown()  Anywhere #### +- +*This method is private* +*This method __updateThrottleDown__ is defined in `prototype` of `PowerQueue`* + +Calling this method will update the throttle on the queue pause tasks. + +> Note: Currently we only support the PowerQueue - but we could support +> a more general interface for pauseable tasks or other usecases. + +> ```PowerQueue.prototype.updateThrottleDown = function() { ...``` [power-queue.js:367](power-queue.js#L367) + +- + +#### *powerqueue*.next([err])  Anywhere #### +- +*This method __next__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __err__ *{string}* (Optional) +Error message if task failed + +- +> * Can pass in `null` to start the queue +> * Passing in a string to `next` will trigger a failure +> * Passing nothing will simply let the next task run +`next` is handed into the [taskHandler](PowerQueue.taskHandler) as a +callback to mark an error or end of current task + +> ```PowerQueue.prototype.next = function(err) { ...``` [power-queue.js:394](power-queue.js#L394) + +- + +#### done  Anywhere #### +- + +__Arguments__ + +* __feedback__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String ](# String )|[ null](# null)}* (Optional) +This allows the task to communicate with the queue + +- + +Explaination of `feedback` +* `Meteor.Error` This means that the task failed in a controlled manner and is allowed to rerun +* `Error` This will throw the passed error - as its an unitended error +* `null` The task is not done yet, rerun later +* `String` The task can perform certain commands on the queue + * "pause" - pause the queue + * "stop" - stop the queue + * "reset" - reset the queue + * "cancel" - cancel the queue + + +> ```PowerQueue.prototype.runTaskDone = function(feedback, invocation) { ...``` [power-queue.js:452](power-queue.js#L452) + +- + +#### *powerqueue*.runTaskDone([feedback], invocation)  Anywhere #### +- +*This method is private* +*This method __runTaskDone__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __feedback__ *{[Meteor.Error ](#Meteor.Error )|[ Error ](# Error )|[ String ](# String )|[ null](# null)}* (Optional) +This allows the task to communicate with the queue +* __invocation__ *{object}* + +- + +> Note: `feedback` is explained in [Done callback](#done) + + +> ```PowerQueue.prototype.runTaskDone = function(feedback, invocation) { ...``` [power-queue.js:452](power-queue.js#L452) + +- + +#### *powerqueue*.runTask(invocation)  Anywhere #### +- +*This method is private* +*This method __runTask__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __invocation__ *{object}* +The object stored in the micro-queue + +- + +> ```PowerQueue.prototype.runTask = function(invocation) { ...``` [power-queue.js:521](power-queue.js#L521) + +- + +#### *powerqueue*.queueTaskHandler()  Anywhere #### +- +*This method __queueTaskHandler__ is defined in `prototype` of `PowerQueue`* +This method handles tasks that are sub queues + +> ```PowerQueue.prototype.queueTaskHandler = function(subQueue, next, failures) { ...``` [power-queue.js:555](power-queue.js#L555) + +- + +#### *powerqueue*.taskHandler  Anywhere #### +- +*This callback __taskHandler__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +This can be data or functions +* __next__ *{function}* +Function `next` call this to end task +* __failures__ *{number}* +Number of failures on this task + +- + +Default task handler expects functions as data: +```js + self.taskHandler = function(data, next, failures) { + // This default task handler expects invocation to be a function to run + if (typeof data !== 'function') { + throw new Error('Default task handler expects a function'); + } + try { + // Have the function call next + data(next, failures); + } catch(err) { + // Throw to fail this task + next(err); + } + }; +``` + +> ```PowerQueue.prototype.taskHandler = function(data, next, failures) { ...``` [power-queue.js:601](power-queue.js#L601) + +- + +#### *powerqueue*.errorHandler  Anywhere #### +- +*This callback __errorHandler__ is defined in `prototype` of `PowerQueue`* + +__Arguments__ + +* __data__ *{any}* +This can be data or functions +* __addTask__ *{function}* +Use this function to insert the data into the queue again +* __failures__ *{number}* +Number of failures on this task + +- + +The default callback: +```js + var foo = new PowerQueue(); + + // Overwrite the default action + foo.errorHandler = function(data, addTask, failures) { + // This could be overwritten the data contains the task data and addTask + // is a helper for adding the task to the queue + // try again: addTask(data); + // console.log('Terminate at ' + failures + ' failures'); + }; +``` + +> ```PowerQueue.prototype.errorHandler = function(data, addTask, failures) { ...``` [power-queue.js:634](power-queue.js#L634) + +- + +#### *powerqueue*.pause()  Anywhere #### +- +*This method __pause__ is defined in `prototype` of `PowerQueue`* + +__TODO__ +``` +* We should have it pause all processing tasks +``` + +> ```PowerQueue.prototype.pause = function() { ...``` [power-queue.js:645](power-queue.js#L645) + +- + +#### *powerqueue*.resume()  Anywhere #### +- +*This method __resume__ is defined in `prototype` of `PowerQueue`* + +__TODO__ +``` +* We should have it resume all processing tasks +``` + +> This will not start a stopped queue + +> ```PowerQueue.prototype.resume = function() { ...``` [power-queue.js:665](power-queue.js#L665) + +- + +#### *powerqueue*.run()  Anywhere #### +- +*This method __run__ is defined in `prototype` of `PowerQueue`* +> Using this command will resume a paused queue and will +> start a stopped queue. + +> ```PowerQueue.prototype.run = function() { ...``` [power-queue.js:677](power-queue.js#L677) + +- diff --git a/packages/wekan-cfs-power-queue/package.js b/packages/wekan-cfs-power-queue/package.js new file mode 100644 index 000000000..757fd06c2 --- /dev/null +++ b/packages/wekan-cfs-power-queue/package.js @@ -0,0 +1,27 @@ +Package.describe({ + name: 'wekan-cfs-power-queue', + version: '0.9.11', + summary: "PowerQueue is a powerful tool for handling async tasks, throtling etc.", + git: 'https://github.com/zcfs/Meteor-power-queue.git' +}); + +Package.onUse(function (api) { + api.versionsFrom('1.0'); + + api.use(['deps', 'wekan-cfs-reactive-property@0.0.4'], ['client', 'server']); + + // We let the user decide what spinal queue to use - We support both + // reactive-list and micro-queue they obey the spinal-queue spec + api.use(['wekan-cfs-reactive-list@0.0.9', 'wekan-cfs-micro-queue@0.0.6'], ['client', 'server'], { weak: true }); + + api.export && api.export('PowerQueue'); + api.addFiles(['power-queue.js'], ['client', 'server']); +}); + +Package.onTest(function (api) { + api.use(['wekan-cfs-power-queue', 'wekan-cfs-reactive-list']); + api.use('test-helpers', ['server', 'client']); + api.use('tinytest'); + + api.addFiles('tests.js'); +}); diff --git a/packages/wekan-cfs-power-queue/power-queue.js b/packages/wekan-cfs-power-queue/power-queue.js new file mode 100644 index 000000000..e1e323163 --- /dev/null +++ b/packages/wekan-cfs-power-queue/power-queue.js @@ -0,0 +1,727 @@ +// Rig weak dependencies +if (typeof MicroQueue === 'undefined' && Package['micro-queue']) { + MicroQueue = Package['micro-queue'].MicroQueue; +} +if (typeof ReactiveList === 'undefined' && Package['reactive-list']) { + ReactiveList = Package['reactive-list'].ReactiveList; +} + +// Rig weak dependencies in +0.9.1 +if (typeof MicroQueue === 'undefined' && Package['wekan-cfs-micro-queue']) { + MicroQueue = Package['wekan-cfs-micro-queue'].MicroQueue; +} +if (typeof ReactiveList === 'undefined' && Package['wekan-cfs-reactive-list']) { + ReactiveList = Package['wekan-cfs-reactive-list'].ReactiveList; +} + +/** + * Creates an instance of a power queue // Testing inline comment + * [Check out demo](http://power-queue-test.meteor.com/) + * + * @constructor + * @self powerqueue + * @param {object} [options] Settings + * @param {boolean} [options.filo=false] Make it a first in last out queue + * @param {boolean} [options.isPaused=false] Set queue paused + * @param {boolean} [options.autostart=true] May adding a task start the queue + * @param {string} [options.name="Queue"] Name of the queue + * @param {number} [options.maxProcessing=1] Limit of simultanous running tasks + * @param {number} [options.maxFailures = 5] Limit retries of failed tasks, if 0 or below we allow infinite failures + * @param {number} [options.jumpOnFailure = true] Jump to next task and retry failed task later + * @param {boolean} [options.debug=false] Log verbose messages to the console + * @param {boolean} [options.reactive=true] Set whether or not this queue should be reactive + * @param {boolean} [options.onAutostart] Callback for the queue autostart event + * @param {boolean} [options.onPaused] Callback for the queue paused event + * @param {boolean} [options.onReleased] Callback for the queue release event + * @param {boolean} [options.onEnded] Callback for the queue end event + * @param {[SpinalQueue](spinal-queue.spec.md)} [options.spinalQueue] Set spinal queue uses pr. default `MicroQueue` or `ReactiveList` if added to the project + */ +PowerQueue = function(options) { + var self = this; + var test = 5; + + self.reactive = (options && options.reactive === false) ? false : true; + + // Allow user to use another micro-queue #3 + // We try setting the ActiveQueue to MicroQueue if installed in the app + var ActiveQueue = (typeof MicroQueue !== 'undefined') && MicroQueue || undefined; + + // If ReactiveList is added to the project we use this over MicroQueue + ActiveQueue = (typeof ReactiveList !== 'undefined') && ReactiveList || ActiveQueue; + + // We allow user to overrule and set a custom spinal-queue spec complient queue + if (options && typeof options.spinalQueue !== 'undefined') { + ActiveQueue = options.spinalQueue; + } + + if (typeof ActiveQueue === 'undefined') { + console.log('Error: You need to add a spinal queue to the project'); + console.log('Please add "micro-queue", "reactive-list" to the project'); + throw new Error('Please add "micro-queue", "reactive-list" or other spinalQueue compatible packages'); + } + + // Default is fifo lilo + self.invocations = new ActiveQueue({ + // + sort: (options && (options.filo || options.lifo)), + reactive: self.reactive + }); + //var self.invocations = new ReactiveList(queueOrder); + + // List of current tasks being processed + self._processList = new ActiveQueue({ + reactive: self.reactive + }); //ReactiveList(); + + // Max number of simultanious tasks being processed + self._maxProcessing = new ReactiveProperty(options && options.maxProcessing || 1, self.reactive); + + // Reactive number of tasks being processed + self._isProcessing = new ReactiveProperty(0, self.reactive); + + // Boolean indicating if queue is paused or not + self._paused = new ReactiveProperty((options && options.isPaused || false), self.reactive); + + // Boolean indicator for queue status active / running (can still be paused) + self._running = new ReactiveProperty(false, self.reactive); + + // Counter for errors, errors are triggered if maxFailures is exeeded + self._errors = new ReactiveProperty(0, self.reactive); + + // Counter for task failures, contains error count + self._failures = new ReactiveProperty(0, self.reactive); + + // On failure jump to new task - if false the current task is rerun until error + self._jumpOnFailure = (options && options.jumpOnFailure === false) ? false : true; + + // Count of all added tasks + self._maxLength = new ReactiveProperty(0, self.reactive); + + // Boolean indicate whether or not a "add" task is allowed to start the queue + self._autostart = new ReactiveProperty( ((options && options.autostart === false) ? false : true), self.reactive); + + // Limit times a task is allowed to fail and be rerun later before triggering an error + self._maxFailures = new ReactiveProperty( (options && options.maxFailures || 5), self.reactive); + + // Name / title of this queue - Not used - should deprecate + self.title = options && options.name || 'Queue'; + + // debug - will print error / failures passed to next + self.debug = !!(options && options.debug); + + /** @method PowerQueue.total + * @reactive + * @returns {number} The total number of tasks added to this queue + */ + self.total = self._maxLength.get; + + /** @method PowerQueue.isPaused + * @reactive + * @returns {boolean} Status of the paused state of the queue + */ + self.isPaused = self._paused.get; + + /** @method PowerQueue.processing + * @reactive + * @returns {number} Number of tasks currently being processed + */ + self.processing = self._isProcessing.get; + + /** @method PowerQueue.errors + * @reactive + * @returns {number} The total number of errors + * Errors are triggered when [maxFailures](PowerQueue.maxFailures) are exeeded + */ + self.errors = self._errors.get; + + /** @method PowerQueue.failures + * @reactive + * @returns {number} The total number of failed tasks + */ + self.failures = self._failures.get; + + /** @method PowerQueue.isRunning + * @reactive + * @returns {boolean} True if the queue is running + * > NOTE: The task can be paused but marked as running + */ + self.isRunning = self._running.get; + + /** @method PowerQueue.maxProcessing Get setter for maxProcessing + * @param {number} [max] If not used this function works as a getter + * @reactive + * @returns {number} Maximum number of simultaneous processing tasks + * + * Example: + * ```js + * foo.maxProcessing(); // Works as a getter and returns the current value + * foo.maxProcessing(20); // This sets the value to 20 + * ``` + */ + self.maxProcessing = self._maxProcessing.getset; + + self._maxProcessing.onChange = function() { + // The user can change the max allowed processing tasks up or down here... + // Update the throttle up + self.updateThrottleUp(); + // Update the throttle down + self.updateThrottleDown(); + }; + + /** @method PowerQueue.autostart Get setter for autostart + * @param {boolean} [autorun] If not used this function works as a getter + * @reactive + * @returns {boolean} If adding a task may trigger the queue to start + * + * Example: + * ```js + * foo.autostart(); // Works as a getter and returns the current value + * foo.autostart(true); // This sets the value to true + * ``` + */ + self.autostart = self._autostart.getset; + + /** @method PowerQueue.maxFailures Get setter for maxFailures + * @param {number} [max] If not used this function works as a getter + * @reactive + * @returns {number} The maximum for failures pr. task before triggering an error + * + * Example: + * ```js + * foo.maxFailures(); // Works as a getter and returns the current value + * foo.maxFailures(10); // This sets the value to 10 + * ``` + */ + self.maxFailures = self._maxFailures.getset; + + /** @callback PowerQueue.onPaused + * Is called when queue is ended + */ + self.onPaused = options && options.onPaused || function() { + self.debug && console.log(self.title + ' ENDED'); + }; + + /** @callback PowerQueue.onEnded + * Is called when queue is ended + */ + self.onEnded = options && options.onEnded || function() { + self.debug && console.log(self.title + ' ENDED'); + }; + + /** @callback PowerQueue.onRelease + * Is called when queue is released + */ + self.onRelease = options && options.onRelease || function() { + self.debug && console.log(self.title + ' RELEASED'); + }; + + /** @callback PowerQueue.onAutostart + * Is called when queue is auto started + */ + self.onAutostart = options && options.onAutostart || function() { + self.debug && console.log(self.title + ' Autostart'); + }; +}; + + /** @method PowerQueue.prototype.processList + * @reactive + * @returns {array} List of tasks currently being processed + */ + PowerQueue.prototype.processingList = function() { + var self = this; + return self._processList.fetch(); + }; + + /** @method PowerQueue.prototype.isHalted + * @reactive + * @returns {boolean} True if the queue is not running or paused + */ + PowerQueue.prototype.isHalted = function() { + var self = this; + return (!self._running.get() || self._paused.get()); + }; + + /** @method PowerQueue.prototype.length + * @reactive + * @returns {number} Number of tasks left in queue to be processed + */ + PowerQueue.prototype.length = function() { + var self = this; + return self.invocations.length(); + }; + + /** @method PowerQueue.prototype.progress + * @reactive + * @returns {number} 0 .. 100 % Indicates the status of the queue + */ + PowerQueue.prototype.progress = function() { + var self = this; + var progress = self._maxLength.get() - self.invocations.length() - self._isProcessing.get(); + if (self._maxLength.value > 0) { + return Math.round(progress / self._maxLength.value * 100); + } + return 0; + }; + + /** @method PowerQueue.prototype.usage + * @reactive + * @returns {number} 0 .. 100 % Indicates resource usage of the queue + */ + PowerQueue.prototype.usage = function() { + var self = this; + return Math.round(self._isProcessing.get() / self._maxProcessing.get() * 100); + }; + + /** @method PowerQueue.prototype.reset Reset the queue + * Calling this will: + * * stop the queue + * * paused to false + * * Discart all queue data + * + * > NOTE: At the moment if the queue has processing tasks they can change + * > the `errors` and `failures` counters. This could change in the future or + * > be prevented by creating a whole new instance of the `PowerQueue` + */ + PowerQueue.prototype.reset = function() { + var self = this; + self.debug && console.log(self.title + ' RESET'); + self._running.set(false); + self._paused.set(false); + self.invocations.reset(); + self._processList.reset(); + + // // Loop through the processing tasks and reset these + // self._processList.forEach(function(data) { + // if (data.queue instanceof PowerQueue) { + // data.queue.reset(); + // } + // }, true); + self._maxLength.set(0); + self._failures.set(0); + self._errors.set(0); + }; + + /** @method PowerQueue._autoStartTasks + * @private + * + * This method defines the autostart algorithm that allows add task to trigger + * a start of the queue if queue is not paused. + */ + PowerQueue.prototype._autoStartTasks = function() { + var self = this; + + // We dont start anything by ourselfs if queue is paused + if (!self._paused.value) { + + // Queue is not running and we are set to autostart so we start the queue + if (!self._running.value && self._autostart.value) { + // Trigger callback / event + self.onAutostart(); + // Set queue as running + self._running.set(true); + } + + // Make sure that we use all available resources + if (self._running.value) { + // Call next to start up the queue + self.next(null); + } + + } + }; + + /** @method PowerQueue.prototype.add + * @param {any} data The task to be handled + * @param {number} [failures] Used internally to Pass on number of failures. + */ + PowerQueue.prototype.add = function(data, failures, id) { + var self = this; + + // Assign new id to task + var assignNewId = self._jumpOnFailure || typeof id === 'undefined'; + + // Set the task id + var taskId = (assignNewId) ? self._maxLength.value + 1 : id; + + // self.invocations.add({ _id: currentId, data: data, failures: failures || 0 }, reversed); + self.invocations.insert(taskId, { _id: taskId, data: data, failures: failures || 0 }); + + // If we assigned new id then increase length + if (assignNewId) self._maxLength.inc(); + + self._autoStartTasks(); + }; + + /** @method PowerQueue.prototype.updateThrottleUp + * @private + * + * Calling this method will update the throttle on the queue adding tasks. + * + * > Note: Currently we only support the PowerQueue - but we could support + * > a more general interface for pauseable tasks or other usecases. + */ + PowerQueue.prototype.updateThrottleUp = function() { + var self = this; + + // How many additional tasks can we handle? + var availableSlots = self._maxProcessing.value - self._isProcessing.value; + // If we can handle more, we have more, we're running, and we're not paused + if (!self._paused.value && self._running.value && availableSlots > 0 && self.invocations._length > 0) { + // Increase counter of current number of tasks being processed + self._isProcessing.inc(); + // Run task + self.runTask(self.invocations.getFirstItem()); + // Repeat recursively; this is better than a for loop to avoid blocking the UI + self.updateThrottleUp(); + } + + }; + + /** @method PowerQueue.prototype.updateThrottleDown + * @private + * + * Calling this method will update the throttle on the queue pause tasks. + * + * > Note: Currently we only support the PowerQueue - but we could support + * > a more general interface for pauseable tasks or other usecases. + */ + PowerQueue.prototype.updateThrottleDown = function() { + var self = this; + // Calculate the differece between acutuall processing tasks and target + var diff = self._isProcessing.value - self._maxProcessing.value; + + // If the diff is more than 0 then we have many tasks processing. + if (diff > 0) { + // We pause the latest added tasks + self._processList.forEachReverse(function(data) { + if (diff > 0 && data.queue instanceof PowerQueue) { + diff--; + // We dont mind calling pause on multiple times on each task + // theres a simple check going on preventing any duplicate actions + data.queue.pause(); + } + }, true); + } + }; + + /** @method PowerQueue.prototype.next + * @param {string} [err] Error message if task failed + * > * Can pass in `null` to start the queue + * > * Passing in a string to `next` will trigger a failure + * > * Passing nothing will simply let the next task run + * `next` is handed into the [taskHandler](PowerQueue.taskHandler) as a + * callback to mark an error or end of current task + */ + PowerQueue.prototype.next = function(err) { + var self = this; + // Primary concern is to throttle up because we are either: + // 1. Starting the queue + // 2. Starting next task + // + // This function does not shut down running tasks + self.updateThrottleUp(); + + // We are running, no tasks are being processed even we just updated the + // throttle up and we got no errors. + // 1. We are paused and releasing tasks + // 2. We are done + if (self._running.value && self._isProcessing.value === 0 && err !== null) { + + // We have no tasks processing so this queue is now releasing resources + // this could be that the queue is paused or stopped, in that case the + // self.invocations._length would be > 0 + // If on the other hand the self.invocations._length is 0 then we have no more + // tasks in the queue so the queue has ended + self.onRelease(self.invocations._length); + + if (!self.invocations._length) { // !self._paused.value && + // Check if queue is done working + // Stop the queue + self._running.set(false); + // self.invocations.reset(); // This should be implicit + self.onEnded(); + } + + } + }; + + /** @callback done + * @param {Meteor.Error | Error | String | null} [feedback] This allows the task to communicate with the queue + * + * Explaination of `feedback` + * * `Meteor.Error` This means that the task failed in a controlled manner and is allowed to rerun + * * `Error` This will throw the passed error - as its an unitended error + * * `null` The task is not done yet, rerun later + * * `String` The task can perform certain commands on the queue + * * "pause" - pause the queue + * * "stop" - stop the queue + * * "reset" - reset the queue + * * "cancel" - cancel the queue + * + */ + + + /** @method PowerQueue.prototype.runTaskDone + * @private + * @param {Meteor.Error | Error | String | null} [feedback] This allows the task to communicate with the queue + * @param {object} invocation + * + * > Note: `feedback` is explained in [Done callback](#done) + * + */ + // Rig the callback function + PowerQueue.prototype.runTaskDone = function(feedback, invocation) { + var self = this; + + // If the task handler throws an error then add it to the queue again + // we allow this for a max of self._maxFailures + // If the error is null then we add the task silently back into the + // microQueue in reverse... This could be due to pause or throttling + if (feedback instanceof Meteor.Error) { + // We only count failures if maxFailures are above 0 + if (self._maxFailures.value > 0) invocation.failures++; + self._failures.inc(); + + // If the user has set the debug flag we print out failures/errors + self.debug && console.error('Error: "' + self.title + '" ' + feedback.message + ', ' + feedback.stack); + + if (invocation.failures < self._maxFailures.value) { + // Add the task again with the increased failures + self.add(invocation.data, invocation.failures, invocation._id); + } else { + self._errors.inc(); + self.errorHandler(invocation.data, self.add, invocation.failures); + } + + // If a error is thrown we assume its not intended + } else if (feedback instanceof Error) throw feedback; + + if (feedback) + + // We use null to throttle pauseable tasks + if (feedback === null) { + // We add this task into the queue, no questions asked + self.invocations.insert(invocation._id, { data: invocation.data, failures: invocation.failures, _id: invocation._id }); + } + + // If the user returns a string we got a command + if (feedback === ''+feedback) { + var command = { + 'pause': function() { self.pause(); }, + 'stop': function() { self.stop(); }, + 'reset': function() { self.reset(); }, + 'cancel': function() { self.cancel(); }, + }; + if (typeof command[feedback] === 'function') { + // Run the command on this queue + command[feedback](); + } else { + // We dont recognize this command, throw an error + throw new Error('Unknown queue command "' + feedback + '"'); + } + } + // Decrease the number of tasks being processed + // make sure we dont go below 0 + if (self._isProcessing.value > 0) self._isProcessing.dec(); + // Task has ended we remove the task from the process list + self._processList.remove(invocation._id); + + invocation.data = null; + invocation.failures = null; + invocation._id = null; + invocation = null; + delete invocation; + // Next task + Meteor.setTimeout(function() { + self.next(); + }, 0); + + }; + + + /** @method PowerQueue.prototype.runTask + * @private // This is not part of the open api + * @param {object} invocation The object stored in the micro-queue + */ + PowerQueue.prototype.runTask = function(invocation) { + var self = this; + + // We start the fitting task handler + // Currently we only support the PowerQueue but we could have a more general + // interface for tasks that allow throttling + try { + if (invocation.data instanceof PowerQueue) { + + // Insert PowerQueue into process list + self._processList.insert(invocation._id, { id: invocation._id, queue: invocation.data }); + // Handle task + self.queueTaskHandler(invocation.data, function subQueueCallbackDone(feedback) { + self.runTaskDone(feedback, invocation); + }, invocation.failures); + + } else { + + // Insert task into process list + self._processList.insert(invocation._id, invocation.data); + // Handle task + self.taskHandler(invocation.data, function taskCallbackDone(feedback) { + self.runTaskDone(feedback, invocation); + }, invocation.failures); + + } + } catch(err) { + throw new Error('Error while running taskHandler for queue, Error: ' + err.message); + } + }; + + /** @method PowerQueue.prototype.queueTaskHandler + * This method handles tasks that are sub queues + */ + PowerQueue.prototype.queueTaskHandler = function(subQueue, next, failures) { + var self = this; + // Monitor sub queue task releases + subQueue.onRelease = function(remaining) { + // Ok, we were paused - this could be throttling so we respect this + // So when the queue is halted we add it back into the main queue + if (remaining > 0) { + // We get out of the queue but dont repport error and add to run later + next(null); + } else { + // Queue has ended + // We simply trigger next task when the sub queue is complete + next(); + // When running subqueues it doesnt make sense to track failures and retry + // the sub queue - this is sub queue domain + } + }; + + // Start the queue + subQueue.run(); + }; + + /** @callback PowerQueue.prototype.taskHandler + * @param {any} data This can be data or functions + * @param {function} next Function `next` call this to end task + * @param {number} failures Number of failures on this task + * + * Default task handler expects functions as data: + * ```js + * self.taskHandler = function(data, next, failures) { + * // This default task handler expects invocation to be a function to run + * if (typeof data !== 'function') { + * throw new Error('Default task handler expects a function'); + * } + * try { + * // Have the function call next + * data(next, failures); + * } catch(err) { + * // Throw to fail this task + * next(err); + * } + * }; + * ``` + */ + + // Can be overwrittin by the user + PowerQueue.prototype.taskHandler = function(data, next, failures) { + var self = this; + // This default task handler expects invocation to be a function to run + if (typeof data !== 'function') { + throw new Error('Default task handler expects a function'); + } + try { + // Have the function call next + data(next, failures); + } catch(err) { + // Throw to fail this task + next(err); + } + }; + + /** @callback PowerQueue.prototype.errorHandler + * @param {any} data This can be data or functions + * @param {function} addTask Use this function to insert the data into the queue again + * @param {number} failures Number of failures on this task + * + * The default callback: + * ```js + * var foo = new PowerQueue(); + * + * // Overwrite the default action + * foo.errorHandler = function(data, addTask, failures) { + * // This could be overwritten the data contains the task data and addTask + * // is a helper for adding the task to the queue + * // try again: addTask(data); + * // console.log('Terminate at ' + failures + ' failures'); + * }; + * ``` + */ + PowerQueue.prototype.errorHandler = function(data, addTask, failures) { + var self = this; + // This could be overwritten the data contains the task data and addTask + // is a helper for adding the task to the queue + // try again: addTask(data); + self.debug && console.log('Terminate at ' + failures + ' failures'); + }; + + /** @method PowerQueue.prototype.pause Pause the queue + * @todo We should have it pause all processing tasks + */ + PowerQueue.prototype.pause = function() { + var self = this; + if (!self._paused.value) { + + self._paused.set(true); + // Loop through the processing tasks and pause these + self._processList.forEach(function(data) { + if (data.queue instanceof PowerQueue) { + // Pause the sub queue + data.queue.pause(); + } + }, true); + + // Trigger callback + self.onPaused(); + } + }; + + /** @method PowerQueue.prototype.resume Start a paused queue + * @todo We should have it resume all processing tasks + * + * > This will not start a stopped queue + */ + PowerQueue.prototype.resume = function() { + var self = this; + self.run(); + }; + + /** @method PowerQueue.prototype.run Starts the queue + * > Using this command will resume a paused queue and will + * > start a stopped queue. + */ + PowerQueue.prototype.run = function() { + var self = this; + //not paused and already running or queue empty or paused subqueues + if (!self._paused.value && self._running.value || !self.invocations._length) { + return; + } + + self._paused.set(false); + self._running.set(true); + self.next(null); + }; + + /** @method PowerQueue.prototype.stop Stops the queue + */ + PowerQueue.prototype.stop = function() { + var self = this; + self._running.set(false); + }; + + /** @method PowerQueue.prototype.cancel Cancel the queue + */ + PowerQueue.prototype.cancel = function() { + var self = this; + self.reset(); + }; + diff --git a/packages/wekan-cfs-power-queue/spinal-queue.spec.md b/packages/wekan-cfs-power-queue/spinal-queue.spec.md new file mode 100644 index 000000000..cb2e50eb1 --- /dev/null +++ b/packages/wekan-cfs-power-queue/spinal-queue.spec.md @@ -0,0 +1,151 @@ +#Spinal Queue Spec +This specification declares the interface for the "spinal" queue in `PowerQueue`. +We allready have two implementations the [MicroQueue](https://github.com/zcfs/Meteor-micro-queue) and [ReactiveList](https://github.com/zcfs/Meteor-reactive-list) + +#SpinalQueue +Provides a simple reactive list interface + +#### new SpinalQueue(lifo)  Anywhere #### +- + +__Arguments__ + +* __lifo__ *{boolean}* +Set the order of the queue default is `fifo` + +- +Example: +```js + var list = new SpinalQueue(); + list.insert(1, { text: 'Hello id: 1' }); + list.insert(2, { text: 'Hello id: 2' }); + list.insert(3, { text: 'Hello id: 3' }); + list.update(2, { text: 'Updated 2'}); + list.remove(1); + + list.forEach(function(value, key) { + console.log('GOT: ' + value.text); + }, true); // Set noneReactive = true, default behaviour is reactive + // Return from Template: + Template.hello.list = function() { + return list.fetch(); + }; +``` + +- + +#### *SpinalQueue*.length()  Anywhere #### +- +*This method __length__ is defined in `SpinalQueue`* + +__Returns__ *{number}* __(is reactive)__ +Length of the reactive list + +- + +#### *SpinalQueue*.reset()  Anywhere #### +- +*This method __reset__ is defined in `SpinalQueue`* + +- + +#### *SpinalQueue*.update(key, value)  Anywhere #### +- +*This method __update__ is defined in `SpinalQueue`* + +__Arguments__ + +* __key__ *{string|number}* +Key to update +* __value__ *{any}* +Update with this value + +> Note: Method is currently not used by `PowerQueue` + +- + +#### *SpinalQueue*.insert(key, value)  Anywhere #### +- +*This method __insert__ is defined in `SpinalQueue`* + +__Arguments__ + +* __key__ *{string|number}* +Key to insert +* __value__ *{any}* +Insert item with this value + +- + +#### *SpinalQueue*.remove(key)  Anywhere #### +- +*This method __remove__ is defined in `SpinalQueue`* + +__Arguments__ + +* __key__ *{string|number}* +Key to remove + +- + +#### *SpinalQueue*.getLastItem()  Anywhere #### +- +*This method __getLastItem__ is defined in `SpinalQueue`* + +__Returns__ *{any}* +Pops last item from the list - removes the item from the list + +> Note: Method is currently not used by `PowerQueue` + +- + +#### *SpinalQueue*.getFirstItem()  Anywhere #### +- +*This method __getFirstItem__ is defined in `SpinalQueue`* + +__Returns__ *{any}* +Pops first item from the list - removes the item from the list + + +#### *SpinalQueue*.forEach(f, [noneReactive], [reverse])  Anywhere #### +- +*This method __forEach__ is defined in `SpinalQueue`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + + +#### *SpinalQueue*.forEachReverse(f, [noneReactive])  Anywhere #### +- +*This method __forEachReverse__ is defined in `SpinalQueue`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +#### *SpinalQueue*.fetch([noneReactive])  Anywhere #### +- +*This method __fetch__ is defined in `SpinalQueue`* + +__Arguments__ + +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +__Returns__ *{array}* __(is reactive)__ +List of items + +- diff --git a/packages/wekan-cfs-power-queue/tests.js b/packages/wekan-cfs-power-queue/tests.js new file mode 100644 index 000000000..01cdd6b1f --- /dev/null +++ b/packages/wekan-cfs-power-queue/tests.js @@ -0,0 +1,198 @@ +"use strict"; + +function equals(a, b) { + return !!(JSON.stringify(a) === JSON.stringify(b)); +} + +Tinytest.add('PowerQueue - scope', function(test) { + + test.isTrue(typeof PowerQueue !== 'undefined', 'The PowerQueue scope is missing, please add the power-queue package'); + + +}); + + +// We run 5 tasks in serial mode +Tinytest.addAsync('PowerQueue - test serial run', function (test, onComplete) { + var queue = new PowerQueue({ + name: 'test queue 1', + autostart: false, + maxProcessing: 1, + debug: true, + // When this task is released we do our tests + onEnded: function() { + console.log('It ended'); + // Check that we ran the expected number of tasks + test.equal(counter, 5, 'counter did not match number of tasks'); + // Check that the result was correct + test.equal(result, expectedResult, 'result was unexpected'); + // We are done testing + onComplete(); + } + }); + + var result = ''; + var expectedResult = '12345'; + var counter = 0; + + var checkCounter = function(id, next) { + console.log('test queue 1 - Run task: ' + id); + // Keep a counter + counter++; + // push id to result + result += id; + // call next task + next(); + }; + + + // Add the tasks to the queue + queue.add(function(next) { checkCounter('1', next); }); + queue.add(function(next) { checkCounter('2', next); }); + queue.add(function(next) { checkCounter('3', next); }); + queue.add(function(next) { checkCounter('4', next); }); + queue.add(function(next) { checkCounter('5', next); }); + + // Run the queue + queue.run(); +}); + +// We run 5 tasks in serial mode but pause the queue on 3 +Tinytest.addAsync('PowerQueue - test serial pause', function (test, onComplete) { + var queue = new PowerQueue({ + name: 'test queue 2', + autostart: false, + maxProcessing: 1, + debug: true, + // When this task is released we do our tests + onPaused: function() { + console.log('Its paused'); + // Check that we ran the expected number of tasks + test.equal(counter, 3, 'counter did not match number of tasks'); + // Check that the result was correct + test.equal(result, expectedResult, 'result was unexpected'); + // We are done testing + onComplete(); + } + }); + + var result = ''; + var expectedResult = '123'; + var counter = 0; + + var checkCounter = function(id, next) { + console.log('test queue 2 - Run task: ' + id); + // Keep a counter + counter++; + // push id to result + result += id; + // call next task + if (id === '3') + next('pause') + else + next(); + }; + + + // Add the tasks to the queue + queue.add(function(next) { checkCounter('1', next); }); + queue.add(function(next) { checkCounter('2', next); }); + queue.add(function(next) { checkCounter('3', next); }); + queue.add(function(next) { checkCounter('4', next); }); + queue.add(function(next) { checkCounter('5', next); }); + + // Run the queue + queue.run(); +}); + +// We run 5 tasks in serial mode but pause the queue on 3 +Tinytest.addAsync('PowerQueue - test 2 task in parallel', function (test, onComplete) { + var queue = new PowerQueue({ + name: 'test queue 3', + autostart: false, + maxProcessing: 2, + debug: true, + // When this task is released we do our tests + onEnded: function() { + console.log('Its paused'); + // Check that we ran the expected number of tasks + test.equal(counter, 10, 'counter did not match number of tasks'); + // Check that the result was correct + test.equal(result, expectedResult, 'result was unexpected'); + // We are done testing + onComplete(); + } + }); + + // start 1-----3-------4-------6------------------------9-----------------------X + // 2-----------------5---------------7--------8-----------10------X + // ms 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 + // result 1 2 3 4 5 6 7 8 9 10 + // result 1 3 2 4 5 7 6 8 10 9 + + var wait = { + '1': 10, + '2': 25, + '3': 10, + '4': 10, + '5': 20, + '6': 30, + '7': 10, + '8': 15, + '9': 30, + '10': 10, + }; + + // 1324 + + var result = ''; + var expectedResult = '13245768109'; + var counter = 0; + + var checkCounter = function(id, next) { + console.log('test queue 3 - Run task: ' + id); + // Keep a counter + counter++; + // push id to result + Meteor.setTimeout(function() { + result += id; + // call next task + next(); + }, wait[id] * 5); // give it a factor 2 to make sure we get the correct result + }; + + + // Add the tasks to the queue + queue.add(function(next) { checkCounter('1', next); }); + queue.add(function(next) { checkCounter('2', next); }); + queue.add(function(next) { checkCounter('3', next); }); + queue.add(function(next) { checkCounter('4', next); }); + queue.add(function(next) { checkCounter('5', next); }); + queue.add(function(next) { checkCounter('6', next); }); + queue.add(function(next) { checkCounter('7', next); }); + queue.add(function(next) { checkCounter('8', next); }); + queue.add(function(next) { checkCounter('9', next); }); + queue.add(function(next) { checkCounter('10', next); }); + + // Run the queue + queue.run(); +}); +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equal(actual, expected, message, not) +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-reactive-list/.editorconfig b/packages/wekan-cfs-reactive-list/.editorconfig new file mode 100644 index 000000000..a2cc1c1fe --- /dev/null +++ b/packages/wekan-cfs-reactive-list/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +# Meteor adapted EditorConfig, http://EditorConfig.org +# By RaiX 2013 + +root = true + +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto +# curly_bracket_next_line = true \ No newline at end of file diff --git a/packages/wekan-cfs-reactive-list/.gitignore b/packages/wekan-cfs-reactive-list/.gitignore new file mode 100644 index 000000000..45353da68 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/.gitignore @@ -0,0 +1,2 @@ +/versions.json +.build* diff --git a/packages/wekan-cfs-reactive-list/.jshintrc b/packages/wekan-cfs-reactive-list/.jshintrc new file mode 100644 index 000000000..3f0dbcc61 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/.jshintrc @@ -0,0 +1,115 @@ +//.jshintrc +{ + // JSHint Meteor Configuration File + // Match the Meteor Style Guide + // + // By @raix with contributions from @aldeed and @awatson1978 + // Source https://github.com/raix/Meteor-jshintrc + // + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : true, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "trailing" : true, // true: Prohibit trailing whitespaces + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : 80, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + //"meteor" : false, // Meteor.js + + // Legacy + "nomen" : false, // true: Prohibit dangling `_` in variables + "onevar" : false, // true: Allow only one `var` statement per function + "passfail" : false, // true: Stop on first error + "white" : false, // true: Check against strict whitespace and indentation rules + + // Custom Globals + "predef" : [ + "Meteor", + "Accounts", + "Session", + "Template", + "check", + "Match", + "Deps", + "EJSON", + "Email", + "Package", + "Tinytest", + "Npm", + "Assets", + "Packages", + "process", + "GroundDB", + "_gDB", + "LocalCollection", + "ReactiveList", + "_", + "Random" + ] // additional predefined global variables +} diff --git a/packages/wekan-cfs-reactive-list/.travis.yml b/packages/wekan-cfs-reactive-list/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-reactive-list/LICENSE.md b/packages/wekan-cfs-reactive-list/LICENSE.md new file mode 100644 index 000000000..2a5c5339e --- /dev/null +++ b/packages/wekan-cfs-reactive-list/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-reactive-list/README.md b/packages/wekan-cfs-reactive-list/README.md new file mode 100644 index 000000000..a05b0f7d8 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/README.md @@ -0,0 +1,82 @@ +wekan-cfs-reactive-list [![Build Status](https://travis-ci.org/CollectionFS/Meteor-reactive-list.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-reactive-list) +========= + +~~Looking for maintainers - please reach out!~~ +This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible. + +**If you're looking for an alternative, we highly recommend [Meteor-Files](https://github.com/VeliovGroup/Meteor-Files) by [VeliovGroup](https://github.com/VeliovGroup)** + +--- + +ReactiveList keeps a sortable reactive list of key+value items. It's simple and fast. + +And... It's powered by Meteor's reactive sugar :) + +Kind regards, +Eric (@aldeed) and Morten (@raix) + +Happy coding!! + +#API +[API Documentation](api.md) + +From the docs: +#### new ReactiveList([options], sort)  Anywhere #### +- + +__Arguments__ + +* __options__ *{object}* (Optional) +* __sort__ *{function}* +The sort algorithm to use + +- +Example: +```js + var list = new ReactiveList(); + list.insert(1, { text: 'Hello id: 1' }); + list.insert(2, { text: 'Hello id: 2' }); + list.insert(3, { text: 'Hello id: 3' }); + list.update(2, { text: 'Updated 2'}); + list.remove(1); + + list.forEach(function(value, key) { + console.log('GOT: ' + value.text); + }, true); // Set noneReactive = true, default behaviour is reactive + // Return from Template: + Template.hello.list = function() { + return list.fetch(); + }; +``` + +#### Example of a sort algorithm +Sort can be used to define the order of the list +```js + var list = new ReactiveList({ + sort: function(a, b) { + // a and b are type of { key, value } + // here we sort by the key: + return a.key < b.key; + } + }); +``` +### Object chain +``` + first last + undefined - obj - obj - obj - undefined + (prev value next) (prev value next) (prev value next) +``` + +``` +ReactiveList = function(options) { ... +``` + +See more at [reactive-list.js:46](reactive-list.js#L46). + +# Contribute + +Here's the [complete API documentation](internal.api.md). To update the docs, run `npm install docmeteor`, then + +```bash +$ docmeteor +``` diff --git a/packages/wekan-cfs-reactive-list/api.md b/packages/wekan-cfs-reactive-list/api.md new file mode 100644 index 000000000..0f7eaa5a3 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/api.md @@ -0,0 +1,195 @@ +#ReactiveList +Provides a simple reactive list interface + +#### new ReactiveList([options], sort)  Anywhere #### +- + +__Arguments__ + +* __options__ *{object}* (Optional) +* __sort__ *{function}* +The sort algorithm to use + +- +Example: +```js + var list = new ReactiveList(); + list.insert(1, { text: 'Hello id: 1' }); + list.insert(2, { text: 'Hello id: 2' }); + list.insert(3, { text: 'Hello id: 3' }); + list.update(2, { text: 'Updated 2'}); + list.remove(1); + + list.forEach(function(value, key) { + console.log('GOT: ' + value.text); + }, true); // Set noneReactive = true, default behaviour is reactive + // Return from Template: + Template.hello.list = function() { + return list.fetch(); + }; +``` +####Example of a sort algorithm +Sort can be used to define the order of the list +```js + var list = new ReactiveList({ + sort: function(a, b) { + // a and b are type of { key, value } + // here we sort by the key: + return a.key < b.key; + } + }); +``` +###Object chain +``` + first last + undefined - obj - obj - obj - undefined + (prev value next) (prev value next) (prev value next) +``` + +> ```ReactiveList = function(options) { ...``` [reactive-list.js:46](reactive-list.js#L46) + +- + +#### *reactivelist*.length()  Anywhere #### +- +*This method __length__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{number}* __(is reactive)__ +Length of the reactive list + +> ```ReactiveList.prototype.length = function() { ...``` [reactive-list.js:73](reactive-list.js#L73) + +- + +#### *reactivelist*.reset()  Anywhere #### +- +*This method __reset__ is defined in `prototype` of `ReactiveList`* + +> ```ReactiveList.prototype.reset = function() { ...``` [reactive-list.js:83](reactive-list.js#L83) + +- + +#### *reactivelist*.update(key, value)  Anywhere #### +- +*This method __update__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to update +* __value__ *{any}* +Update with this value + +- + +> ```ReactiveList.prototype.update = function(key, value) { ...``` [reactive-list.js:102](reactive-list.js#L102) + +- + +#### *reactivelist*.insert(key, value)  Anywhere #### +- +*This method __insert__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to insert +* __value__ *{any}* +Insert item with this value + +- + +> ```ReactiveList.prototype.insert = function(key, value) { ...``` [reactive-list.js:118](reactive-list.js#L118) + +- + +#### *reactivelist*.remove(key)  Anywhere #### +- +*This method __remove__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to remove + +- + +> ```ReactiveList.prototype.remove = function(key) { ...``` [reactive-list.js:180](reactive-list.js#L180) + +- + +#### *reactivelist*.getLastItem()  Anywhere #### +- +*This method __getLastItem__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{any}* +Pops last item from the list - removes the item from the list + +> ```ReactiveList.prototype.getLastItem = function(first) { ...``` [reactive-list.js:221](reactive-list.js#L221) + +- + +#### *reactivelist*.getFirstItem()  Anywhere #### +- +*This method __getFirstItem__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{any}* +Pops first item from the list - removes the item from the list + +> ```ReactiveList.prototype.getFirstItem = function() { ...``` [reactive-list.js:239](reactive-list.js#L239) + +- + +#### *reactivelist*.forEach(f, [noneReactive], [reverse])  Anywhere #### +- +*This method __forEach__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity +* __reverse__ *{boolean}* (Optional = false) +Set true to reverse iteration `forEachReverse` + +- + +> ```ReactiveList.prototype.forEach = function(f, noneReactive, reverse) { ...``` [reactive-list.js:249](reactive-list.js#L249) + +- + +#### *reactivelist*.forEachReverse(f, [noneReactive])  Anywhere #### +- +*This method __forEachReverse__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +> ```ReactiveList.prototype.forEachReverse = function(f, noneReactive) { ...``` [reactive-list.js:272](reactive-list.js#L272) + +- + +#### *reactivelist*.fetch([noneReactive])  Anywhere #### +- +*This method __fetch__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +__Returns__ *{array}* __(is reactive)__ +List of items + +> ```ReactiveList.prototype.fetch = function(noneReactive) { ...``` [reactive-list.js:282](reactive-list.js#L282) + +- diff --git a/packages/wekan-cfs-reactive-list/internal.api.md b/packages/wekan-cfs-reactive-list/internal.api.md new file mode 100644 index 000000000..3eb92996d --- /dev/null +++ b/packages/wekan-cfs-reactive-list/internal.api.md @@ -0,0 +1,203 @@ +> File: ["reactive-list.js"](reactive-list.js) +> Where: {client|server} + +- +#ReactiveList +Provides a simple reactive list interface + +#### new ReactiveList([options], sort)  Anywhere #### +- + +__Arguments__ + +* __options__ *{object}* (Optional) +* __sort__ *{function}* +The sort algorithm to use + +- +Example: +```js + var list = new ReactiveList(); + list.insert(1, { text: 'Hello id: 1' }); + list.insert(2, { text: 'Hello id: 2' }); + list.insert(3, { text: 'Hello id: 3' }); + list.update(2, { text: 'Updated 2'}); + list.remove(1); + + list.forEach(function(value, key) { + console.log('GOT: ' + value.text); + }, true); // Set noneReactive = true, default behaviour is reactive + // Return from Template: + Template.hello.list = function() { + return list.fetch(); + }; +``` +####Example of a sort algorithm +Sort can be used to define the order of the list +```js + var list = new ReactiveList({ + sort: function(a, b) { + // a and b are type of { key, value } + // here we sort by the key: + return a.key < b.key; + } + }); +``` +###Object chain +``` + first last + undefined - obj - obj - obj - undefined + (prev value next) (prev value next) (prev value next) +``` + +> ```ReactiveList = function(options) { ...``` [reactive-list.js:46](reactive-list.js#L46) + +- + +#### *reactivelist*.length()  Anywhere #### +- +*This method __length__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{number}* __(is reactive)__ +Length of the reactive list + +> ```ReactiveList.prototype.length = function() { ...``` [reactive-list.js:73](reactive-list.js#L73) + +- + +#### *reactivelist*.reset()  Anywhere #### +- +*This method __reset__ is defined in `prototype` of `ReactiveList`* +__TODO__ +``` +* Check for memory leaks, if so we have to iterate over lookup and delete the items +``` + +> ```ReactiveList.prototype.reset = function() { ...``` [reactive-list.js:83](reactive-list.js#L83) + +- + +#### *reactivelist*.update(key, value)  Anywhere #### +- +*This method __update__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to update +* __value__ *{any}* +Update with this value + +- + +> ```ReactiveList.prototype.update = function(key, value) { ...``` [reactive-list.js:102](reactive-list.js#L102) + +- + +#### *reactivelist*.insert(key, value)  Anywhere #### +- +*This method __insert__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to insert +* __value__ *{any}* +Insert item with this value + +- + +> ```ReactiveList.prototype.insert = function(key, value) { ...``` [reactive-list.js:118](reactive-list.js#L118) + +- + +#### *reactivelist*.remove(key)  Anywhere #### +- +*This method __remove__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __key__ *{string|number}* +Key to remove + +- + +> ```ReactiveList.prototype.remove = function(key) { ...``` [reactive-list.js:180](reactive-list.js#L180) + +- + +#### *reactivelist*.getLastItem()  Anywhere #### +- +*This method __getLastItem__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{any}* +Pops last item from the list - removes the item from the list + +> ```ReactiveList.prototype.getLastItem = function(first) { ...``` [reactive-list.js:221](reactive-list.js#L221) + +- + +#### *reactivelist*.getFirstItem()  Anywhere #### +- +*This method __getFirstItem__ is defined in `prototype` of `ReactiveList`* + +__Returns__ *{any}* +Pops first item from the list - removes the item from the list + +> ```ReactiveList.prototype.getFirstItem = function() { ...``` [reactive-list.js:239](reactive-list.js#L239) + +- + +#### *reactivelist*.forEach(f, [noneReactive], [reverse])  Anywhere #### +- +*This method __forEach__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity +* __reverse__ *{boolean}* (Optional = false) +Set true to reverse iteration `forEachReverse` + +- + +> ```ReactiveList.prototype.forEach = function(f, noneReactive, reverse) { ...``` [reactive-list.js:249](reactive-list.js#L249) + +- + +#### *reactivelist*.forEachReverse(f, [noneReactive])  Anywhere #### +- +*This method __forEachReverse__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __f__ *{function}* +Callback `funciton(value, key)` +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +> ```ReactiveList.prototype.forEachReverse = function(f, noneReactive) { ...``` [reactive-list.js:272](reactive-list.js#L272) + +- + +#### *reactivelist*.fetch([noneReactive])  Anywhere #### +- +*This method __fetch__ is defined in `prototype` of `ReactiveList`* + +__Arguments__ + +* __noneReactive__ *{boolean}* (Optional = false) +Set true if want to disable reactivity + +- + +__Returns__ *{array}* __(is reactive)__ +List of items + +> ```ReactiveList.prototype.fetch = function(noneReactive) { ...``` [reactive-list.js:282](reactive-list.js#L282) + +- diff --git a/packages/wekan-cfs-reactive-list/package.js b/packages/wekan-cfs-reactive-list/package.js new file mode 100644 index 000000000..a96928f78 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/package.js @@ -0,0 +1,23 @@ +Package.describe({ + name: 'wekan-cfs-reactive-list', + version: '0.0.9', + summary: 'ReactiveList provides a small, fast queue/list built for Power-Queue', + git: 'https://github.com/zcfs/Meteor-reactive-list.git' +}); + +Package.onUse(function (api) { + api.versionsFrom('1.0'); + + api.use('deps', ['client', 'server']); + + api.export('ReactiveList'); + api.addFiles(['reactive-list.js'], ['client', 'server']); +}); + +Package.onTest(function (api) { + api.use('wekan-cfs-reactive-list'); + api.use('test-helpers', 'server'); + api.use('tinytest'); + + api.addFiles('tests.js'); +}); diff --git a/packages/wekan-cfs-reactive-list/reactive-list.js b/packages/wekan-cfs-reactive-list/reactive-list.js new file mode 100644 index 000000000..c99434bc4 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/reactive-list.js @@ -0,0 +1,313 @@ +// #ReactiveList +// Provides a simple reactive list interface +var _noopCallback = function() {}; + +var _nonReactive = { + changed: _noopCallback, + depend: _noopCallback +}; + +/** @method ReactiveList Keeps a reactive list of key+value items + * @constructor + * @namespace ReactiveList + * @param {object} [options] + * @param {function} sort The sort algorithm to use + * @param {boolean} [reactive=true] If set false this list is not reactive + * Example: + * ```js + * var list = new ReactiveList(); + * list.insert(1, { text: 'Hello id: 1' }); + * list.insert(2, { text: 'Hello id: 2' }); + * list.insert(3, { text: 'Hello id: 3' }); + * list.update(2, { text: 'Updated 2'}); + * list.remove(1); + * + * list.forEach(function(value, key) { + * console.log('GOT: ' + value.text); + * }, true); // Set noneReactive = true, default behaviour is reactive + * + * // Return from Template: + * Template.hello.list = function() { + * return list.fetch(); + * }; + * ``` + * + * ####Example of a sort algorithm + * Sort can be used to define the order of the list + * ```js + * var list = new ReactiveList({ + * sort: function(a, b) { + * // a and b are type of { key, value } + * // here we sort by the key: + * return a.key < b.key; + * } + * }); + * ``` + * ###Object chain + * ``` + * first last + * undefined - obj - obj - obj - undefined + * (prev value next) (prev value next) (prev value next) + * ``` + */ +ReactiveList = function(options) { + var self = this; + // Object container + self.lookup = {}; + // Length + self._length = 0; + // First object in list + self.first; + // Last object in list + self.last; + // Set sort to options.sort or default to true (asc) + self.sort = (options && options.sort || function(a, b) { + return a.key < b.key; + }); + + // Allow user to disable reactivity, default true + self.isReactive = (options)? options.reactive !== false : true; + + // If lifo queue + if (options === true || options && options.sort === true) { + self.sort = function(a, b) { return a.key > b.key; }; + } + + // Rig the dependencies + self._listDeps = (self.isReactive)? new Deps.Dependency() : _nonReactive; + + self._lengthDeps = (self.isReactive)? new Deps.Dependency() : _nonReactive; +}; + +/** @method ReactiveList.prototype.length Returns the length of the list + * @reactive + * @returns {number} Length of the reactive list + */ +ReactiveList.prototype.length = function() { + var self = this; + // Make this reactive + self._lengthDeps.depend(); + return self._length; +}; + +/** @method ReactiveList.prototype.reset Reset and empty the list + * @todo Check for memory leaks, if so we have to iterate over lookup and delete the items + */ +ReactiveList.prototype.reset = function() { + var self = this; + // Clear the reference to the first object + self.first = undefined; + // Clear the reference to the last object + self.last = undefined; + // Clear the lookup object + self.lookup = {}; + // Set the length to 0 + self._length = 0; + self._lengthDeps.changed(); + // Invalidate the list + self._listDeps.changed(); +}; + +/** @method ReactiveList.prototype.update + * @param {string|number} key Key to update + * @param {any} value Update with this value + */ +ReactiveList.prototype.update = function(key, value) { + var self = this; + // Make sure the key is found in the list + if (typeof self.lookup[key] === 'undefined') { + throw new Error('Reactive list cannot update, key "' + key + '" not found'); + } + // Set the new value + self.lookup[key].value = value; + // Invalidate the list + self._listDeps.changed(); +}; + +/** @method ReactiveList.prototype.insert + * @param {string|number} key Key to insert + * @param {any} value Insert item with this value + */ +ReactiveList.prototype.insert = function(key, value) { + var self = this; + if (typeof self.lookup[key] !== 'undefined') { + throw new Error('Reactive list could not insert: key "' + key + + '" allready found'); + } + // Create the new item to insert into the list + var newItem = { key: key, value: value }; + // Init current by pointing it at the first object in the list + var current = self.first; + // Init the isInserted flag + var isInserted = false; + + + // Iterate through list while not empty and item is not inserted + while (typeof current !== 'undefined' && !isInserted) { + + // Sort the list by using the sort function + if (self.sort(newItem, current)) { + + // Insert self.lookup[key] before + if (typeof current.prev === 'undefined') { self.first = newItem; } + + // Set the references in the inserted object + newItem.prev = current.prev; + newItem.next = current; + + // Update the two existing objects + if (current.prev) { current.prev.next = newItem; } + current.prev = newItem; + + // Mark the item as inserted - job's done + isInserted = true; + } + // Goto next object + current = current.next; + } + + + if (!isInserted) { + // We append it to the list + newItem.prev = self.last; + if (self.last) { self.last.next = newItem; } + + // Update the last pointing to newItem + self.last = newItem; + // Update first if we are appending to an empty list + if (self._length === 0) { self.first = newItem; } + } + + + // Reference the object for a quick lookup option + self.lookup[key] = newItem; + // Increase length + self._length++; + self._lengthDeps.changed(); + // And invalidate the list + self._listDeps.changed(); +}; + +/** @method ReactiveList.prototype.remove + * @param {string|number} key Key to remove + */ +ReactiveList.prototype.remove = function(key) { + var self = this; + // Get the item object + var item = self.lookup[key]; + + // Check that it exists + if (typeof item === 'undefined') { + return; + // throw new Error('ReactiveList cannot remove item, unknow key "' + key + + // '"'); + } + + // Rig the references + var prevItem = item.prev; + var nextItem = item.next; + + // Update chain prev object next reference + if (typeof prevItem !== 'undefined') { + prevItem.next = nextItem; + } else { + self.first = nextItem; + } + + // Update chain next object prev reference + if (typeof nextItem !== 'undefined') { + nextItem.prev = prevItem; + } else { + self.last = prevItem; + } + + // Clean up + self.lookup[key].last = null; + self.lookup[key].prev = null; + self.lookup[key] = null; + prevItem = null; + + delete self.lookup[key]; + // Decrease the length + self._length--; + self._lengthDeps.changed(); + // Invalidate the list + self._listDeps.changed(); +}; + +/** @method ReactiveList.prototype.getLastItem + * @returns {any} Pops last item from the list - removes the item from the list + */ +ReactiveList.prototype.getLastItem = function(first) { + var self = this; + + // Get the relevant item first or last + var item = (first)?self.first: self.last; + + if (typeof item === 'undefined') { + return; // Empty list + } + // Remove the item from the list + self.remove(item.key); + // Return the value + return item.value; +}; + +/** @method ReactiveList.prototype.getFirstItem + * @returns {any} Pops first item from the list - removes the item from the list + */ +ReactiveList.prototype.getFirstItem = function() { + // This gets the first item... + return this.getLastItem(true); +}; + +/** @method ReactiveList.prototype.forEach + * @param {function} f Callback `funciton(value, key)` + * @param {boolean} [noneReactive=false] Set true if want to disable reactivity + * @param {boolean} [reverse=false] Set true to reverse iteration `forEachReverse` + */ +ReactiveList.prototype.forEach = function(f, noneReactive, reverse) { + var self = this; + // Check if f is a function + if (typeof f !== 'function') { + throw new Error('ReactiveList forEach requires a function'); + } + // We allow this not to be reactive + if (!noneReactive) { self._listDeps.depend(); } + // Set current to the first object + var current = (reverse)?self.last: self.first; + // Iterate over the list while its not empty + while (current) { + // Call the callback function + f(current.value, current.key); + // Jump to the next item in the list + current = (reverse)?current.prev: current.next; + } +}; + +/** @method ReactiveList.prototype.forEachReverse + * @param {function} f Callback `funciton(value, key)` + * @param {boolean} [noneReactive=false] Set true if want to disable reactivity + */ +ReactiveList.prototype.forEachReverse = function(f, noneReactive) { + // Call forEach with the reverse flag + this.forEach(f, noneReactive, true); +}; + +/** @method ReactiveList.prototype.fetch Returns list as array + * @param {boolean} [noneReactive=false] Set true if want to disable reactivity + * @reactive This can be disabled + * @returns {array} List of items + */ +ReactiveList.prototype.fetch = function(noneReactive) { + var self = this; + // Init the result buffer + var result = []; + // Iterate over the list items + self.forEach(function fetchCallback(value) { + // Add the item value to the result + result.push(value); + }, noneReactive); + // Return the result + return result; +}; diff --git a/packages/wekan-cfs-reactive-list/tests.js b/packages/wekan-cfs-reactive-list/tests.js new file mode 100644 index 000000000..e13e90318 --- /dev/null +++ b/packages/wekan-cfs-reactive-list/tests.js @@ -0,0 +1,104 @@ +"use strict"; + +function equals(a, b) { + return !!(JSON.stringify(a) === JSON.stringify(b)); +} + +Tinytest.add('ReactiveList - definition', function(test) { + test.isTrue(typeof ReactiveList !== 'undefined', 'ReactiveList is undefined, make sure to add the reactive-list package'); +}); + + +// We do a small test of insert and remove +Tinytest.addAsync('ReactiveList - basic insert and remove - test 1', function test1 (test, onComplete) { + Meteor.setTimeout(function() { + var list = new ReactiveList({ + reactive: true + }); + + var testLength = 500; + + for (var i = 0; i < testLength; i++) + list.insert(i, 'value' + i); + + test.equal(list._length, testLength, 'List length is not as expected'); + + var order = 0; + list.forEach(function(value, key) { + test.equal(key, order, 'order is not as expected'); + test.equal(value, 'value'+order, 'order is not as expected'); + order++; + }); + + test.equal(order, testLength, 'forEach length is not as expected'); + + list.forEachReverse(function(value, key) { + order--; + test.equal(key, order, 'order is not as expected'); + test.equal(value, 'value'+order, 'order is not as expected'); + }); + + test.equal(order, 0, 'forEachReverse length is not as expected'); + + // Remove all items + for (var i = 0; i < testLength; i++) + list.remove(i); + + test.equal(list._length, 0, 'List length is not as expected'); + + test.isUndefined(list.first, 'First should now be undefined'); + + test.isUndefined(list.last, 'Last should now be undefined'); + onComplete(); + }, 1000); +}); + + +// We test insert and remove on larger scale +Tinytest.addAsync('ReactiveList - basic insert and reset - test 2', function test2 (test, onComplete) { + Meteor.setTimeout(function() { + var list = new ReactiveList({ + reactive: true + }); + + + var testLength = 500; + + for (var i = 0; i < testLength; i++) + list.insert(i, 'value' + i); + + test.equal(list._length, testLength, 'List length is not as expected'); + + + // Remove all items + list.reset(); + + test.equal(list._length, 0, 'List length is not as expected'); + + test.isUndefined(list.first, 'First should now be undefined'); + + test.isUndefined(list.last, 'Last should now be undefined'); + + onComplete(); + }, 5000); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-reactive-property/.editorconfig b/packages/wekan-cfs-reactive-property/.editorconfig new file mode 100644 index 000000000..37a7d486e --- /dev/null +++ b/packages/wekan-cfs-reactive-property/.editorconfig @@ -0,0 +1,18 @@ +# .editorconfig +# Meteor adapted EditorConfig, http://EditorConfig.org +# By RaiX 2013 + +root = true + +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 80 +indent_brace_style = 1TBS +spaces_around_operators = true +quote_type = auto +# curly_bracket_next_line = true diff --git a/packages/wekan-cfs-reactive-property/.gitignore b/packages/wekan-cfs-reactive-property/.gitignore new file mode 100644 index 000000000..45353da68 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/.gitignore @@ -0,0 +1,2 @@ +/versions.json +.build* diff --git a/packages/wekan-cfs-reactive-property/.jshintrc b/packages/wekan-cfs-reactive-property/.jshintrc new file mode 100644 index 000000000..52d24acf2 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/.jshintrc @@ -0,0 +1,97 @@ +{ + // JSHint Meteor Configuration File + // Match the Meteor Style Guide + // + // By @raix with contributions from @aldeed and @awatson1978 + // Source https://github.com/raix/Meteor-jshintrc + // + // See http://jshint.com/docs/ for more details + + "maxerr": 50, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "forin": true, + "immed": false, + "indent": 2, + "latedef": false, + "newcap": false, + "noarg": true, + "noempty": true, + "nonew": false, + "plusplus": false, + "quotmark": false, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "maxparams": false, + "maxdepth": false, + "maxstatements": false, + "maxcomplexity": false, + "maxlen": 80, + "asi": false, + "boss": false, + "debug": false, + "eqnull": false, + "es5": false, + "esnext": false, + "moz": false, + "evil": false, + "expr": false, + "funcscope": false, + "globalstrict": true, + "iterator": false, + "lastsemic": false, + "laxbreak": false, + "laxcomma": false, + "loopfunc": false, + "multistr": false, + "proto": false, + "scripturl": false, + "smarttabs": false, + "shadow": false, + "sub": false, + "supernew": false, + "validthis": false, + "browser": true, + "couch": false, + "devel": true, + "dojo": false, + "jquery": false, + "mootools": false, + "node": false, + "nonstandard": false, + "prototypejs": false, + "rhino": false, + "worker": false, + "wsh": false, + "yui": false, + "nomen": false, + "onevar": false, + "passfail": false, + "white": false, + "predef": [ + "Meteor", + "Accounts", + "Session", + "Template", + "check", + "Match", + "Deps", + "EJSON", + "Email", + "Package", + "Tinytest", + "Npm", + "Assets", + "Packages", + "process", + "LocalCollection", + "_", + "Random", + "HTTP", + "_methodHTTP" + ] +} \ No newline at end of file diff --git a/packages/wekan-cfs-reactive-property/LICENSE.md b/packages/wekan-cfs-reactive-property/LICENSE.md new file mode 100644 index 000000000..2a5c5339e --- /dev/null +++ b/packages/wekan-cfs-reactive-property/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-reactive-property/README.md b/packages/wekan-cfs-reactive-property/README.md new file mode 100644 index 000000000..14d5c4a7f --- /dev/null +++ b/packages/wekan-cfs-reactive-property/README.md @@ -0,0 +1,32 @@ +wekan-cfs-reactive-property [![Build Status](https://travis-ci.org/CollectionFS/Meteor-reactive-property.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-reactive-property) +========= + +~~Looking for maintainers - please reach out!~~ +This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible. + +**If you're looking for an alternative, we highly recommend [Meteor-Files](https://github.com/VeliovGroup/Meteor-Files) by [VeliovGroup](https://github.com/VeliovGroup)** + +--- + +ReactiveProperty: +* `get` Get value +* `set` Set value +* `getset` A combined getter and setter +* `inc` Increase numeric values +* `dec` Decrease numeric values + +And... It's powered by Meteor's reactive sugar :) + +Kind regards Eric(@aldeed) and Morten(@raix) + +Happy coding!! + +#API +[API Documentation](api.md) + +#Contribute +[API Complete Documentation](internal.api.md) +Update docs, `npm install docmeteor` +```bash +$ docmeteor +``` diff --git a/packages/wekan-cfs-reactive-property/api.md b/packages/wekan-cfs-reactive-property/api.md new file mode 100644 index 000000000..ce1886f88 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/api.md @@ -0,0 +1,152 @@ +#ReactiveProperty +A simple class that provides an reactive property interface + +#### new ReactiveProperty(defaultValue, [reactive])  Anywhere #### +- + +__Arguments__ + +* __defaultValue__ *{any}* +Set the default value for the reactive property +* __reactive__ *{boolean}* (Optional = true) +Allow the user to disable reactivity + +- + + +This api should only be in the internal.api.md + +> ```ReactiveProperty = function(defaultValue, reactive) { ...``` [reactive-property.js:18](reactive-property.js#L18) + +- + +#### ReactiveProperty.get()  Anywhere #### +- +*This method __get__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.get(); // equals "bar" +``` + +> ```self.get = function() { ...``` [reactive-property.js:44](reactive-property.js#L44) + +- + +#### ReactiveProperty.set(value)  Anywhere #### +- +*This method __set__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __value__ *{any}* + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set('bar'); +``` + +> ```self.set = function(value) { ...``` [reactive-property.js:58](reactive-property.js#L58) + +- + +#### ReactiveProperty.dec([by])  Anywhere #### +- +*This method __dec__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __by__ *{number}* (Optional = 1) +Value to decrease by + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set(0); + foo.dec(5); // -5 +``` + +> ```self.dec = function(by) { ...``` [reactive-property.js:75](reactive-property.js#L75) + +- + +#### ReactiveProperty.inc([by])  Anywhere #### +- +*This method __inc__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __by__ *{number}* (Optional = 1) +Value to increase by + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set(0); + foo.inc(5); // 5 +``` + +> ```self.inc = function(by) { ...``` [reactive-property.js:90](reactive-property.js#L90) + +- + +#### ReactiveProperty.getset([value])  Anywhere #### +- +*This method __getset__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __value__ *{any}* (Optional) +Value to set property - if undefined the act like `get` + +- + +__Returns__ *{any}* +Returns value if no arguments are passed to the function + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.getset(5); + foo.getset(); // returns 5 +``` + +> ```self.getset = function(value) { ...``` [reactive-property.js:106](reactive-property.js#L106) + +- + +#### ReactiveProperty.toString()  Anywhere #### +- +*This method __toString__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.toString(); // returns 'bar' +``` + +> ```self.toString = function() { ...``` [reactive-property.js:122](reactive-property.js#L122) + +- + +#### ReactiveProperty.toText()  Anywhere #### +- +*This method __toText__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.toText(); // returns 'bar' +``` + +> ```self.toText = self.toString;``` [reactive-property.js:135](reactive-property.js#L135) + +- diff --git a/packages/wekan-cfs-reactive-property/internal.api.md b/packages/wekan-cfs-reactive-property/internal.api.md new file mode 100644 index 000000000..74b4b6607 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/internal.api.md @@ -0,0 +1,167 @@ +> File: ["reactive-property.js"](reactive-property.js) +> Where: {client|server} + +- +#ReactiveProperty +A simple class that provides an reactive property interface + +#### new ReactiveProperty(defaultValue, [reactive])  Anywhere #### +- + +__Arguments__ + +* __defaultValue__ *{any}* +Set the default value for the reactive property +* __reactive__ *{boolean}* (Optional = true) +Allow the user to disable reactivity + +- + + +This api should only be in the internal.api.md + +> ```ReactiveProperty = function(defaultValue, reactive) { ...``` [reactive-property.js:18](reactive-property.js#L18) + +- + +#### ReactiveProperty.value {any}  Anywhere #### +- +*This property is private* +*This property __value__ is defined in `ReactiveProperty`* +This contains the non reactive value, should only be used as a getter for +internal use + +> ```self.value = defaultValue;``` [reactive-property.js:27](reactive-property.js#L27) + +- + +#### ReactiveProperty.get()  Anywhere #### +- +*This method __get__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.get(); // equals "bar" +``` + +> ```self.get = function() { ...``` [reactive-property.js:44](reactive-property.js#L44) + +- + +#### ReactiveProperty.set(value)  Anywhere #### +- +*This method __set__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __value__ *{any}* + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set('bar'); +``` + +> ```self.set = function(value) { ...``` [reactive-property.js:58](reactive-property.js#L58) + +- + +#### ReactiveProperty.dec([by])  Anywhere #### +- +*This method __dec__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __by__ *{number}* (Optional = 1) +Value to decrease by + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set(0); + foo.dec(5); // -5 +``` + +> ```self.dec = function(by) { ...``` [reactive-property.js:75](reactive-property.js#L75) + +- + +#### ReactiveProperty.inc([by])  Anywhere #### +- +*This method __inc__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __by__ *{number}* (Optional = 1) +Value to increase by + +- + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.set(0); + foo.inc(5); // 5 +``` + +> ```self.inc = function(by) { ...``` [reactive-property.js:90](reactive-property.js#L90) + +- + +#### ReactiveProperty.getset([value])  Anywhere #### +- +*This method __getset__ is defined in `ReactiveProperty`* + +__Arguments__ + +* __value__ *{any}* (Optional) +Value to set property - if undefined the act like `get` + +- + +__Returns__ *{any}* +Returns value if no arguments are passed to the function + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.getset(5); + foo.getset(); // returns 5 +``` + +> ```self.getset = function(value) { ...``` [reactive-property.js:106](reactive-property.js#L106) + +- + +#### ReactiveProperty.toString()  Anywhere #### +- +*This method __toString__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.toString(); // returns 'bar' +``` + +> ```self.toString = function() { ...``` [reactive-property.js:122](reactive-property.js#L122) + +- + +#### ReactiveProperty.toText()  Anywhere #### +- +*This method __toText__ is defined in `ReactiveProperty`* + +Usage: +```js + var foo = new ReactiveProperty('bar'); + foo.toText(); // returns 'bar' +``` + +> ```self.toText = self.toString;``` [reactive-property.js:135](reactive-property.js#L135) + +- diff --git a/packages/wekan-cfs-reactive-property/package.js b/packages/wekan-cfs-reactive-property/package.js new file mode 100644 index 000000000..cc8797e96 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/package.js @@ -0,0 +1,23 @@ +Package.describe({ + name: 'wekan-cfs-reactive-property', + version: '0.0.4', + summary: 'Reactive Property is a small, fast reative property class', + git: 'https://github.com/zcfs/Meteor-reactive-property.git' +}); + +Package.onUse(function (api) { + api.versionsFrom('1.0'); + + api.use('deps', ['client', 'server']); + + api.export('ReactiveProperty'); + api.addFiles(['reactive-property.js'], ['client', 'server']); +}); + +// Package.onTest(function (api) { +// api.use('power-queue'); +// api.use('test-helpers', 'server'); +// api.use('tinytest'); + +// api.add_files('tests.js'); +// }); diff --git a/packages/wekan-cfs-reactive-property/reactive-property.js b/packages/wekan-cfs-reactive-property/reactive-property.js new file mode 100644 index 000000000..183f3aea9 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/reactive-property.js @@ -0,0 +1,137 @@ +// #ReactiveProperty +// A simple class that provides an reactive property interface + +_noopCallback = function() {}; + +_nonReactive = { + changed: _noopCallback, + depend: _noopCallback +}; + +/** + * @constructor + * @param {any} defaultValue Set the default value for the reactive property + * @param {boolean} [reactive = true] Allow the user to disable reactivity + * + * This api should only be in the internal.api.md + */ +ReactiveProperty = function(defaultValue, reactive) { + var self = this; + var _deps = (reactive === false)? _nonReactive : new Deps.Dependency(); + + /** @property ReactiveProperty.value + * @private + * This contains the non reactive value, should only be used as a getter for + * internal use + */ + self.value = defaultValue; + + self.onChange = function() {}; + + self.changed = function() { + _deps.changed(); + self.onChange(self.value); + }; + + /** + * @method ReactiveProperty.get + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.get(); // equals "bar" + * ``` + */ + self.get = function() { + _deps.depend(); + return self.value; + }; + + /** + * @method ReactiveProperty.set Set property to value + * @param {any} value + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.set('bar'); + * ``` + */ + self.set = function(value) { + if (self.value !== value) { + self.value = value; + self.changed(); + } + }; + + /** + * @method ReactiveProperty.dec Decrease numeric property + * @param {number} [by=1] Value to decrease by + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.set(0); + * foo.dec(5); // -5 + * ``` + */ + self.dec = function(by) { + self.value -= by || 1; + self.changed(); + }; + + /** + * @method ReactiveProperty.inc increase numeric property + * @param {number} [by=1] Value to increase by + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.set(0); + * foo.inc(5); // 5 + * ``` + */ + self.inc = function(by) { + self.value += by || 1; + self.changed(); + }; + + /** + * @method ReactiveProperty.getset increase numeric property + * @param {any} [value] Value to set property - if undefined the act like `get` + * @returns {any} Returns value if no arguments are passed to the function + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.getset(5); + * foo.getset(); // returns 5 + * ``` + */ + self.getset = function(value) { + if (typeof value !== 'undefined') { + self.set(value); + } else { + return self.get(); + } + }; + + /** + * @method ReactiveProperty.toString + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.toString(); // returns 'bar' + * ``` + */ + self.toString = function() { + var val = self.get(); + return val ? val.toString() : ''; + }; + + /** + * @method ReactiveProperty.toText + * Usage: + * ```js + * var foo = new ReactiveProperty('bar'); + * foo.toText(); // returns 'bar' + * ``` + */ + self.toText = self.toString; + +}; diff --git a/packages/wekan-cfs-reactive-property/tests.js b/packages/wekan-cfs-reactive-property/tests.js new file mode 100644 index 000000000..863e96821 --- /dev/null +++ b/packages/wekan-cfs-reactive-property/tests.js @@ -0,0 +1,30 @@ +"use strict"; + +function equals(a, b) { + return !!(JSON.stringify(a) === JSON.stringify(b)); +} + +Tinytest.add('ReactiveProperty - test', function(test) { + //TODO +}); + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-standard-packages/.travis.yml b/packages/wekan-cfs-standard-packages/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-standard-packages/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-standard-packages/ADVANCED.md b/packages/wekan-cfs-standard-packages/ADVANCED.md new file mode 100644 index 000000000..8e8b64012 --- /dev/null +++ b/packages/wekan-cfs-standard-packages/ADVANCED.md @@ -0,0 +1,265 @@ +This is advanced information useful for anyone who contributes to CollectionFS +or wants to make their own storage adapter. + +## Goals + +* Scale horizontally and vertically +* Secure file operations and file serving +* Limit memory consumption and stream where possible +* Use queues to provide synchronous execution of tasks +* Reactivity +* Uploads and downloads are cancelable and resumable + +## All Packages + +* wekan-cfs-standard-packages (implies some component packages) + * wekan-cfs-base-package + * wekan-cfs-file + * wekan-cfs-collection + * wekan-cfs-collection-filters + * wekan-cfs-access-point + * wekan-cfs-worker + * wekan-cfs-upload-http +* wekan-cfs-graphicsmagick +* wekan-cfs-ui +* wekan-cfs-filesystem +* wekan-cfs-gridfs +* wekan-cfs-s3 +* wekan-cfs-dropbox + +## Collections + +Various MongoDB collections are created by CollectionFS and related packages. +Here's an explanation of what they are named and what their documents look like. + +### cfs.collectionName.filerecord (FS.Collection) + +```js +{ + _id: "", + copies: { + storeName: { + key: String, // a string that the store generates, understands, and uses to uniquely identify the file in the backing store + name: String, // as saved in this store, potentially changed by beforeWrite + type: String, // as saved in this store, potentially changed by beforeWrite + size: Number, // as saved in this store, in bytes, potentially changed by beforeWrite + createdAt: Date, // when first saved in this store + updatedAt: Date // when last saved in this store + } + }, + original: { + name: String, // of the originally uploaded file + type: String, // of the originally uploaded file + size: Number, // of the originally uploaded file, in bytes + updatedAt: Date // of the originally uploaded file + }, + failures: { + copies: { + storeName: { + count: Number, + firstAttempt: Date, + lastAttempt: Date, + doneTrying: Boolean + } + } + } +} +``` + +The `original` object represents the file that was uploaded. Each object under `copies`, represents the file that was stored, keyed by store name. + +The original file isn’t technically stored anywhere permanently, so the `original` object can be generally ignored. It’s there for reference in case you want to see any of that info. + +The objects under the `copies` object represent the copies of the original that were actually saved in the backing stores. The info here might be the same as the original file, or it might be different if your store has a `beforeWrite` or `transformWrite` function that changes the stored file's properties. + +You can change any of the info in the `copies` object using the setter methods on FS.File instances, like fileObj.name('NewName.gif', {store: 'blobs'}), which would change the name of the file stored in the blobs store. Of course you wouldn't want to change `type` or `size` unless you were also transforming the binary file data itself. The important thing is not to touch the `key` property since that's used by the store to retrieve the file data. The rest is metadata that you're free to change. + +### cfs_gridfs... + +Created by the GridFS storage adapter. The `cfs_gridfs..files` and `cfs_gridfs..chunks` collections match the MongoDB GridFS spec. + +### _tempstore + +When files are first uploaded or inserted on the server, we save the original to a temporary store. This is a place where data can be stored until we have all the chunks we need and we are able to successfully save the file to all of the defined permanant stores. The temporary store may create one or more collections with `_tempstore` in the name. In general, it is relatively safe to clear these collections at any time. The worst you will do is cause a currently uploading or currently storing file to fail. + +## Creating a Storage Adapter + +To create a storage adapter, define an object constructor function that takes +a name as a first argument and any additional necessary settings as additional +arguments. Make it return an instance of FS.StorageAdapter, passing your API to +the FS.StorageAdapter constructor function. Here's an example: + +```js +FS.MyStore = function(name, options) { + // Prep some variables here + + return new FS.StorageAdapter(name, {}, { + typeName: 'storage.myadapter', + get: function(identifier, callback) { + // Use identifier to retrieve a Buffer and pass it to callback + }, + getBytes: function(identifier, start, end, callback) { + // Use identifier to retrieve a Buffer containing only + // the bytes from start to end, and pass it to callback. + // If this is impossible, don't include a getBytes property. + }, + put: function(id, fileKey, buffer, options, callback) { + // Store the buffer and then call the callback, passing it + // an identifier that will later be the first argument of the + // other API functions. The identifier will likely be the + // fileKey, the id, or some altered version of those. + }, + remove: function(identifier, callback) { + // Delete the data for the file identified by identifier + }, + watch: function(callback) { + // If you can watch file data, initialize a watcher and then call + // callback whenever a file changes. Refer to the filesystem + // storage adapter for an example. If you can't watch files, then + // throw an error stating that the "sync" option is not supported. + }, + init: function() { + // Perform any initialization + } + }); +}; +``` + +`getBytes` and `init` are optional. The others are required, but you should throw +an error from `watch` if you can't watch files. Your `put` function should check +for an `overwrite` option. If it's true, save the data even if you've already +saved for the given `id` or `fileKey`. If not, you may alter the `fileKey` as +necessary to prevent overwriting and then pass the altered `fileKey` to the +callback. + +By convention, any official stores should be in the `FS` namespace +and end with the word "Store". + +## Architecture + +``` +Client <---- (ddp/http) --- | CFS access point | + | Security layer | + | Storage adapters | + | | | | + Mongo–––––––––O | | | + Local––––––––––––––O | | + Fileserver––––––––––––––O | + External server––––––––––––––O +``` + +## The Upload Process (DDP) + +Here's a closer look at what happens when a file is uploaded. + +### Step 1: Insert + +All uploads initiate on a client. If a file is inserted on the server, the +data is already on the server, so no upload process is necessary. + +An upload begins when you call `insert()` on an `FS.Collection` instance. The +`insert` method inserts the file's metadata into the underlying collection on +the client. If that is successful, it immediately calls `put()` on the `FS.File` +instance. This in turn calls `FS.uploadQueue.uploadFile()`, which kicks off +the data upload. + +### Step 2: Transfer + +The upload transfer queue's `uploadFile` method uses the file size and a +pre-defined chunk size to determine how many chunks the file will be broken into. +It then adds one task to its PowerQueue instance per chunk. Each chunk task does +the following: + +1. Extracts the necessary subset (chunk) of data from the file. +2. Passes the data chunk to a server method over DDP. +3. Marks the chunk uploaded in a client-only tracking collection if the server +method reports no errors. + +PowerQueue's reactive methods are used to report total progress for all current +uploads, but the custom client-only tracking collection is used to report progress +per file. (TODO: update this after PQ sub-queues are done and the transfer +queues are updated to use them) + +### Step 3: Receive + +Each data chunk is received by a DDP access point (a server method defined in +accessPoint.js). This method does the following: + +1. Checks that incoming arguments are of the correct type. +2. If the `insecure` package is not used, checks the `insert` allow and deny +functions. If insertion is denied, the method throws an access denied error. +3. Passes the data chunk to the temporary store. +4. If the temporary store reports that it now has all chunks for the file, +retrieves the complete file from the temporary store and calls `fsFile.put()` +to begin the process of saving it to each of the defined stores. + +### Step 4: Temporarily Store + +The temporary store is managed by the `FS.TempStore` object. When the server method +calls `FS.TempStore.saveChunk()`, the chunk data is saved to a randomly named +temporary file in the server operating system's default temporary directory. +The path to this file is saved in the `chunks` array on the corresponding FS.File. +(The `chunks` property is available on an FS.File instance only on the server +because it is used strictly for server-side tracking.) + +`saveChunk` calls a callback after saving the chunk. The second argument of +this callback is `true` if all chunks for the given file are now saved in the +temporary store (i.e., all chunks were uploaded so all file data is now present +on the server). + +Using a temporary filesystem store like this ensures that chunks remain available +after an app or server restart, allowing the client to resume uploads. Chunks +are saved to the app server filesystem rather than GridFS in your MongoDB because it +is theoretically faster, especially when the mongo database is on a separate server. + +NOTE: The first version of temp storage appended/wrote to a single temp file +per uploaded file, but there are issues with file locking and writing to +specific start-points in an existing file. By switching to 1 chunk = 1 temp +file, we have the freedom to have parallel chunk uploads in any order without +fear of file lock contention, and it's still really fast. + +### Step 5: Store + +After the server method calls `put()` on the complete uploaded file, this in +turn calls `saveCopies()` on its associated `FS.Collection` instance. `saveCopies` +loops through each copy/store you defined in the options and saves the file +to that store. If you defined a `beforeSave` function for the store, the file +passes through that function first. If a `beforeSave` returns `false`, this is +recorded in the `FS.File` instance and the file will never be saved to that store. + +* If the store reports that it successfully saved the file, a reference string, +returned by the store, is stored in `fsFile.copies[storeName]`. This string will +be anything that the store wants to return, so long as it can be used later to +retrieve that same file. +* If the store reports that it was not able to save the file, the error is logged +in the `FS.File` instance. If the total number of failures exceeds the `maxTries` +setting for the store, a `doneTrying` flag is set to `true`. When `doneTrying` +is `false`, the FileWorker may attempt to save again later. (See the next step.) + +### Step 6: Retry + +The `FileWorker` (in fileWorker.js), searches all `FS.Collection` every 5 +seconds (not configurable right now but could be) to see if there are any +storage failures that meet the criteria to be +retried (namely, if failure count > 0 and doneTrying is false). The file worker +then calls `saveCopies` for each file that is identified. (See the previous step.) + +The `FileWorker` also does one more thing. If it identifies any files that have +been successfully saved to all defined stores, or that have failed to save the +maximum number of times for a store, or that won't be saved to a store because +`beforeSave` returned `false`, then it tells `FS.TempStore` to delete all the +temporary chunks for that file. They are no longer needed. + +Note: One the temporary chunks are deleted, the "original" file will be no longer +available if all stores are modifying the original using a `beforeSave` function. + +## The Download Process (DDP) + +Here's a closer look at what happens when a file is downloaded. + +TODO Explain with similar layout to "The Upload Process" section + +## Wish List + +* Dynamic file manipulation +* Paste box upload component diff --git a/packages/wekan-cfs-standard-packages/CHANGELOG.md b/packages/wekan-cfs-standard-packages/CHANGELOG.md new file mode 100644 index 000000000..342de524c --- /dev/null +++ b/packages/wekan-cfs-standard-packages/CHANGELOG.md @@ -0,0 +1,721 @@ +# Changelog + +## vCurrent +## [v0.5.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.2) +#### 17/12/14 by Morten Henriksen +- Bump to version 0.5.2 + +## [v0.5.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.1) +#### 17/12/14 by Morten Henriksen +- mbr update, remove versions.json + +- Bump to version 0.5.1 + +## [v0.5.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.5.0) +#### 17/12/14 by Morten Henriksen +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- mbr update versions and fix warnings + +- update pkg, dependencies, etc. + +- Fix #483 + +- *Merged pull-request:* "mrt graphicsmagick instructions to meteor 0.9+" [#442](https://github.com/zcfs/Meteor-CollectionFS/issues/442) ([yogiben](https://github.com/yogiben)) + +- mrt graphicsmagick instructions to meteor 0.9+ + +- mention underlying collection + +- *Merged pull-request:* "Added server-side Buffer example to README.md" [#405](https://github.com/zcfs/Meteor-CollectionFS/issues/405) ([abuddenb](https://github.com/abuddenb)) + +- change prop names + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- add "Display an Uploaded Image" example + +- addition to steps + +- correct 0.9.0 steps + +- Merge branch 'devel' of github.com:abuddenb/Meteor-CollectionFS into devel + +- Added server-side Buffer example to README.md + +Patches by GitHub users [@yogiben](https://github.com/yogiben), [@abuddenb](https://github.com/abuddenb). + +## [v0.4.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.9) +#### 27/08/14 by Eric Dobbertin +- add 0.9.0 instructions + +- change package name to lowercase + +- Added server-side Buffer example to README.md + +- *Merged pull-request:* "Updated README.md to reflect that FS.File - Objects can't be stored in t..." [#395](https://github.com/zcfs/Meteor-CollectionFS/issues/395) ([DanielDornhardt](https://github.com/DanielDornhardt)) + +- Updated README.md to reflect that FS.File - Objects can't be stored in the Server MongoDB at the moment. + +- document key + +- update the descriptions of collections + +- document beforeWrite + +- add download button example + +- *Merged pull-request:* "Update README.md" [#354](https://github.com/zcfs/Meteor-CollectionFS/issues/354) ([karabijavad](https://github.com/karabijavad)) + +- should say to use cfs-graphicsmagick pkg + +- fix typo + +- *Merged pull-request:* "Added "Storing FS.File references in your objects" chapter to README" [#311](https://github.com/zcfs/Meteor-CollectionFS/issues/311) ([Sanjo](https://github.com/Sanjo)) + +- Added "Storing FS.File references in your objects" chapter to README + +- a few more API updates + +- updates for changed FS.File API + +- additions and clarifications + +- *Merged pull-request:* "Custom Metadata misleading example" [#282](https://github.com/zcfs/Meteor-CollectionFS/issues/282) ([czeslaaw](https://github.com/czeslaaw)) + +- Custom Metadata misleading example + +Patches by GitHub users [@DanielDornhardt](https://github.com/DanielDornhardt), [@karabijavad](https://github.com/karabijavad), [@Sanjo](https://github.com/Sanjo), [@czeslaaw](https://github.com/czeslaaw). + +## [v0.4.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.8) +#### 09/04/14 by Eric Dobbertin +- Add cfs-collection-filters pkg by default + +- Use FS.Utility.setFileExtension in example + +## [v0.4.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.7) +#### 05/04/14 by Morten Henriksen +- corrections, and move image examples here so we can deprecate cfs-imagemagick + +## [v0.4.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.6) +#### 02/04/14 by Morten Henriksen +- *Fixed bug:* "Documentation Issues" [#241](https://github.com/zcfs/Meteor-CollectionFS/issues/241) + +- point to cfs-graphicsmagick readme for transform examples + +## [v0.4.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.5) +#### 31/03/14 by Eric Dobbertin +- use latest releases + +- Add yet a note about mrt and collectionFS in the readme + +## [v0.4.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.4) +#### 25/03/14 by Morten Henriksen +- attempt to get everything up to date + +- Add notice about mrt having troubles figuring out deps + +- *Merged pull-request:* "Removed redundant installation instructions." [#212](https://github.com/zcfs/Meteor-CollectionFS/issues/212) ([lleonard188](https://github.com/lleonard188)) + +- up to date + +- read me update + +- add test note + +Patches by GitHub user [@lleonard188](https://github.com/lleonard188). + +## [v0.4.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.3) +#### 23/03/14 by Morten Henriksen +- use collectionFS travis version force update + +## [v0.4.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.2) +#### 22/03/14 by Morten Henriksen +- change deps + +- Add read me about transformWrite + +## [v0.4.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.1) +#### 21/03/14 by Morten Henriksen +- update reference to devel branch + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS into devel + +- don't need to install transfer + +- mark deprecated api docs + +- test strikeout md + +- no need to imply cfs-transfer since it doesn't do anything at the moment + +- Remove ejson-file imply + +## [v0.4.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.4.0) +#### 06/03/14 by Eric Dobbertin +- add section for documenting example code for common tasks, and add some of the examples; @raix feel free to add more + +- add http upload package to smart.json + +- Merge origin/devel into devel + +- need to imply the http upload package because it's the default now + +- update HTTP access point override docs + +- update docs to use `devel` branch + +- list component packages in smart.json + +- Merge origin/devel into devel-merge + +- add the ejson-file and correct doc + +- refactor everything into packages + +## [(origin/devel-merge-old] (https://github.com/zcfs/Meteor-CollectionFS/tree/(origin/devel-merge-old) +#### 12/02/14 by Eric Dobbertin +- update to latest FileSaver.js + +- one of many refactores + +- add optimization section + +- add section explaining client vs server insert + +- prevent autopublish for the SA collections + +- remove arg that isn't used or passed + +- changes to support client SA, plus clean/reorg SA code to have less duplication + +- merge the concepts of "store" and "copy", update docs, switch to FS.Store namespace + +- use wait:true to avoid Meteor issue + +- extend allow when insecure package is installed + +- document and improve code flow a bit + +- faster to use onResultReceived callback + +- use onload instead of onloadend because we want only successful loads + +- remove some console logging + +- fix issue where stuff wasn't uploading because we weren't calling getFileRecord() in the access point methods + +- make allowed file extension checks not be case sensitive + +- remove DDP "/del" access point since it's not used or necessary + +- Remove collection-hooks dependency; use deny instead. Also some cleanup and code docs + +- replace `mmmagic` dependency with a `mime` dependency; hopefully fixes issues we've seen with meteor deploy + +- refactor to expose saveCopy to API; then change fileworker observes to be per-copy, calling saveCopy and allowing better control of which copies to create at which times + +- use observes for all store saving and temp store deleting; add/adjust some api doc comments (didn't regenerate yet) + +- improve fileIsAllowed check order and messages + +- Add check for the Accounts package + +- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120) + +- always append access token to url when a user is logged in; makes usage simpler to do opt-out rather than opt-in + +- Don't queue ddp method calls + +- Merge changes + +- Optimize buffer handling + +- Switch to MicroQueue + +- improve memory management, attempt client side resume implementation, other minor fixes + +- fix several issues, make downloading work, improve a few bits of code + +- do the isImage test correctly; add some API docs + +- isEmpty will be true for null or undefined + +- rewrite getExtension so it works for unmounted files, too + +- add strong reactive-list dependency for powerqueue + +- minor fixes and code improvements; track uploading files by both collection and ID since one queue handles all collections + +- Base the upload queue on PowerQueue sub queues + +- Add sa note + +- limit use of db + +- Refactor and documentation + +- use mmmagic 0.3.5 + +- *Fixed bug:* "Option to set Cache-Control and Max-Age" [#117](https://github.com/zcfs/Meteor-CollectionFS/issues/117) + +- fix stuff that's broken + +## [v0.3.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.7) +#### 08/01/14 by Morten Henriksen +- *Fixed bug:* "How to store files on the server side?" [#29](https://github.com/zcfs/Meteor-CollectionFS/issues/29) + +- rework ejson and remove fsFile.reload + +- explain complete upload process in ADVANCED docs + +- Adds join to smart.json (its a weak dependency #119) + +- link to api docs for acceptDropsOn + +- typo/formatting fixes + +- docs + insert callback returns `FS.File` instead of `id` + +- Add docs and `FS.File.fetch` + +- Add support for `Join` + +- end with line + +- smaller headlines in docs + +- init api docs + +- Refactor and documentation + +- FS.Collection.insert should return the `FS.File` object + +- remove some comments and such + +- document custom connections + +- add livedata ref to access DDP obj + +- use separate ddp connection with option to pass in custom + +- public folder and gm/im + +- fix null options + +- *Fixed bug:* "no userId passed to download allow validator" [#120](https://github.com/zcfs/Meteor-CollectionFS/issues/120) + +- make useHTTP true by default + +- skip auth checks if Package.insecure + +- remove `callback` arg from fsFile.get since it's not used or necessary; fix partial gets such that they actually use getBytes, greatly speeding up downloads of large files + +- reorg code and speed up downloads + +- split TransferQueue into DownloadTransferQueue and UploadTransferQueue + +- improvements to make use of new PowerQueue features + +- fix issue with previous commit + +- add accessPoints option + +- Pull out temporary chunk code into a separate tempStore.js file, within a TempStore object. This makes it easier to maintain. Also updated the file worker code to correctly find temporary chunks that can be removed and delete those files. + +- add security section + +- Internally, change all "master" stuff to be the same as "copies". External API is still the same, but master options are copied to a special copy named "_master" so that all the other code can be cleaner. This may be a step toward being able to blur or eliminate the master/copy distinction, although there are still some benefits to having a master. + +- Add instructions for installing for testing + +- refactor code to be a bit cleaner + +- add functions for getting an FS.File or setting FS.File data from a URL on the server + +- clean up and improve some transfer code + +- update console log message to be more correct + +- ensure that fsFile.bytesUploaded is always set correctly + +- fix some issues with recent commits + +- *Merged pull-request:* "Updates the filter example area so that it works" [#109](https://github.com/zcfs/Meteor-CollectionFS/issues/109) ([cramhead](https://github.com/cramhead)) + +- add .npm to gitignore + +- Remove .npm folder + +- Clean clone just a bit + +- Refactor fsCollection and argParser + +- Comment on filter options + +- Add download url + +- refactor access point + +- add put/get/del security based on allow/deny functions + +- Updates the filter example area so that it works + +- update for API change + +- more client-side speed improvements and allow passing File/Blob to FS.File constructor again + +- fix data mixup + +- fix upload slowness and blocking + +- change API to adjust issue with data loading callbacks + +- revise FS.File API where data handling is concerned; fix some issues with callback handling in client-side methods; upshot should be faster, smoother uploads and downloads + +- change names and put everything in exported FS namespace + +- fix get/download of copies + +- *Fixed bug:* "Files after certain size aren't saved properly. " [#104](https://github.com/zcfs/Meteor-CollectionFS/issues/104) + +- add fileobject metadata and acceptDropsOn + +- add some methods to load FO data from URL + +- Correct allow/deny examples + +- call put callback correctly + +- add correct temporary installation instructions + +- use correct filename when saving download + +- filtering fixes + +- removed some unused stuff + +- adjust some comments + +- switch api.remove to api.del for consistency with the other methods + +- add hasCopy method + +- new api; tons of changes + +- use generic queue for server file handling + +- update progress in the correct place + +- minor changes to comments + +- fix gridfs get method + +- incorporate #82 + +- make filtering work (added collection-hooks dependency for core package) + +- change UploadsCollection to CollectionFS; change former CollectionFS to GridFS and don't export it (used only by the gridfs storage adaptor); clean up some other areas and update readmes + +- *Fixed bug:* "retrieveBlob failure" [#93](https://github.com/zcfs/Meteor-CollectionFS/issues/93) + +- implement http methods URLs + +- Merge branch 'devel' of https://github.com/zcfs/Meteor-CollectionFS.git into devel + +- significant revisions to move downloading support to the UploadsCollection and make collectionFS/gridFS a pure storage adaptor + +- Merge branch 'pr/94' into devel + +- split and revise readmes + +- rename packages and organize package.js + +- error handling improvements + +- better failure handling for removeCopy + +- handlebars helper to display blob image in CFS package + +- remove all encoding info + +- refactor to fix multiple-file simultaneous uploads + +- don't use _id in filesystem destination since it's not set anyway + +- remove FileObject.file and instead save file as Blob (.blob) when FileObject.fromFile is called + +- revise API a bit + +- stop using strings and encoding and pass everything as Uint8Array (fixes downloading corruption) + +- only attempt to delete file if it exists + +- remove unused file + +- complete refactoring; temporary for testing/tweaking and then will split into multiple packages + +- *Merged pull-request:* "Implemented max parallel transfers" [#62](https://github.com/zcfs/Meteor-CollectionFS/issues/62) ([floo51](https://github.com/floo51)) + +Patches by GitHub users [@cramhead](https://github.com/cramhead), [@floo51](https://github.com/floo51). + +## [v0.3.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.6) +#### 10/10/13 by Morten Henriksen +- Edit ideas about storage adapters and filehandlers + +- Add MIT License + +- Add paypal and weak deps + +- Added the org. filemanager demo/example + +## [v0.3.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.5) +#### 20/09/13 by Morten Henriksen +## [v0.3.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.4) +#### 20/09/13 by Morten Henriksen +- Extract the examples from collectionFS + +- Added examples from @mxab + +## [v0.3.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.3) +#### 18/09/13 by Morten Henriksen +- added travis badge + +- Added travis badge + +- Added basic environment + +- Added metadata getter to docs + +## [v0.3.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.2) +#### 17/09/13 by Morten Henriksen +## [v0.3.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.3.1) +#### 16/09/13 by Morten Henriksen +- Added some details about http.publishing of collections + +- added check in filehandlers + +- Updated som docs + +- js hint added scope + +- jshint clean up + +- *Fixed bug:* "Exception from setTimeout callback: { stack: [Getter] }" [#64](https://github.com/zcfs/Meteor-CollectionFS/issues/64) + +- Merge branch 'devel' + +- Bump version to preview of 0.3.0 + +- Implemented max parallel transfers + +- Revert "began refractoring" + +- Revert "Run jshint through files" + +- Revert "Preparing file object methods for better api" + +- Revert "added fileobject files to package" + +- added fileobject files to package + +- Preparing file object methods for better api + +- Run jshint through files + +- *Merged pull-request:* "Improvements and Fixes to built-in helpers, storeFiles, and acceptDropsOn" [#51](https://github.com/zcfs/Meteor-CollectionFS/issues/51) ([aldeed](https://github.com/aldeed)) + +- Add generic events system, switch to "enums" for invalid event types, and change "fileFilter" to "filter" throughout. Update README to reflect these changes. + +- Update readme to reflect storeFiles and acceptDropsOn changes, plus add documentation for all of the new built-in handlebars helpers + +- commit some files that should be committed + +- -Improve and fix built-in handlebars helpers -Prefix all built-in helpers with "cfs" -Update new example app to reflect helper changes, and improve and fix it a bit, too + +- Merge remote-tracking branch 'upstream/master' + +- update documentation of storeFiles and acceptDropsOn + +- Added credit to @eprochasson + +- *Merged pull-request:* "Should fix issue #45" [#50](https://github.com/zcfs/Meteor-CollectionFS/issues/50) ([eprochasson](https://github.com/eprochasson)) + +- *Fixed bug:* "file handlers not showing up in local demo" [#45](https://github.com/zcfs/Meteor-CollectionFS/issues/45) + +- *Merged pull-request:* "Update README.md" [#49](https://github.com/zcfs/Meteor-CollectionFS/issues/49) ([eprochasson](https://github.com/eprochasson)) + +- Added meteor style guide for jshint + +- fix package file + +- fixes and improvements to storeFiles() and acceptDropsOn() + +- Use a different saveAs shim + +- Merge branch 'master' of https://github.com/aldeed/Meteor-CollectionFS + +- Fixes and changes to support cfs changes + +- -"cfs" prefix, new helpers, improvements and fixes -include saveAs shim in the package so that download button helper can reliably call it + +- Add dependencies and files + +- Document storeFiles() and acceptDropsOn() + +- *Merged pull-request:* "Built-ins, fixes, etc." [#48](https://github.com/zcfs/Meteor-CollectionFS/issues/48) ([aldeed](https://github.com/aldeed)) + +- Add the new files to the package manifest + +- Add underscore as dependency. It seems that Meteor may soon remove underscore from the core. + +- Minor changes to support fileHanders() and fileFilter() function changes + +- Copy in numeral.js for use by the built-in handlebar helper that displays file size in human readable format. This means the helper supports any of the format strings supported by numeral.js for file sizes. + +- -Add fileFilter() function to specify allowed and disallowed files per collectionFS, based on extensions and/or content types -Add fileIsAllowed() function to easily check whether a particular file is allowed based on the rules set up by fileFilter() -Change all of the passthrough functions (find, findOne, update, remove, allow, deny) to pass through all function arguments more simply and more safely. This allows, for example, using find() instead of find({}). -Change fileHandlers() to extend the object whenever called, which means you can safely call it more than once -Add several utility functions for use in either client or server code + +- -Add storeFiles API -Check that files are allowed by fileFilter before saving -Add acceptDropsOn API -Use .depend() instead of Deps throughout -Pull out _getProgress calc to use in two places -Add isUploading API -Improve isDownloading code + +- New file to hold built-in handlebars helpers, including several initial helpers + +- New file manager example app showing how to use new features, built-in handlebars helpers, etc. + +- *Merged pull-request:* "Minor doc correction" [#47](https://github.com/zcfs/Meteor-CollectionFS/issues/47) ([aldeed](https://github.com/aldeed)) + +- *Merged pull-request:* "Documentation Improvement" [#46](https://github.com/zcfs/Meteor-CollectionFS/issues/46) ([aldeed](https://github.com/aldeed)) + +- Extensively revised README + +Patches by GitHub users [@aldeed](https://github.com/aldeed), [@eprochasson](https://github.com/eprochasson). + +## [v0.2.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.3) +#### 04/05/13 by Morten Henriksen +- *Fixed bug:* "Binary File Transfers Corrupted? Truncated?" [#41](https://github.com/zcfs/Meteor-CollectionFS/issues/41) + +## [v0.2.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.2) +#### 03/05/13 by Morten Henriksen +- updated scope for serverConsole + +- *Fixed bug:* "Error when using fresh meteor install" [#40](https://github.com/zcfs/Meteor-CollectionFS/issues/40) + +- more text edits + +- Minor text edits + +- Add credit to README + +- *Merged pull-request:* "Option for file encoding" [#36](https://github.com/zcfs/Meteor-CollectionFS/issues/36) ([nhibner](https://github.com/nhibner)) + +- Typo fix. + +- Updated documentation (added encoding to the fileRecord structure). + +- File encoding is stored in the fileRecord. + +- Allow the user to specify an encoding for the buffer when storing on the server. + +- *Merged pull-request:* "Fix backwards incompatibility" [#33](https://github.com/zcfs/Meteor-CollectionFS/issues/33) ([mitar](https://github.com/mitar)) + +- Fix backwards incompatibility. + +Patches by GitHub users [@nhibner](https://github.com/nhibner), [@mitar](https://github.com/mitar). + +## [v0.2.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.1) +#### 08/04/13 by Morten Henriksen +- Minor fixes, added dragndrop, minor refractoring + +- Only work on completed files + +## [v0.2.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.2.0) +#### 06/04/13 by Morten Henriksen +- Bump to 0.2.0 - Nice + +- Big speed and refractoring + +## [v0.1.9] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.9) +#### 31/03/13 by Morten Henriksen +- Added some more doc, deprecated autosubscribe to autopublish instead + +## [v0.1.8] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.8) +#### 31/03/13 by Morten Henriksen +- Added maxFilehandlers to the doc + +## [(origin/devel-old, origin/devel-#27-fixed] (https://github.com/zcfs/Meteor-CollectionFS/tree/(origin/devel-old, origin/devel-#27-fixed) +#### 31/03/13 by Morten Henriksen +- Added documentation by @petrocket + +- Got filehandlers up and running in bundles + +- Make a seperate thread + connection foreach collectionFS + +## [v0.1.7] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.7) +#### 14/03/13 by Morten Henriksen +## [v0.1.6] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.6) +#### 13/01/13 by Morten Henriksen +- Converted .length to string to cope with Meteor use of underscore + +## [(origin/devel-server-cache] (https://github.com/zcfs/Meteor-CollectionFS/tree/(origin/devel-server-cache) +#### 11/01/13 by Morten Henriksen +## [v0.1.5] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.5) +#### 11/01/13 by Morten Henriksen +- On going tests - db updates gone, requires refresh to commit changes to db - guess some que not working + +- removed setTimeout when spawn == 1 + +## [v0.1.4] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.4) +#### 08/01/13 by Morten Henriksen +## [v0.1.3] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.3) +#### 08/01/13 by Morten Henriksen +## [v0.1.2] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.2) +#### 08/01/13 by Morten Henriksen +## [v0.1.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.1) +#### 08/01/13 by Morten Henriksen +- Big one, added fileHandler to create cashed versions of the file + +- added made with Meteor in fileHandler example + +- corrected param bug in find and findOne + +- minor fix and update + +- Corrected install guide to use Meteorite + +## [v0.1.0] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1.0) +#### 07/01/13 by Morten Henriksen +## [v0.1] (https://github.com/zcfs/Meteor-CollectionFS/tree/v0.1) +#### 07/01/13 by Morten Henriksen +- updated file strukture and example + +- Restructure to package system + +- Refractoring filenames and edit package.js + +- Package.js added + +- Added smart.json for Atmosphere packages - Not tested! + +- Readme styling corrected + +- Added comments to the upload, storeFile + +- StoreFile should return fileId or null + +- Added notes about security + +- Added only owner can resume, makes sense for now + +- Added how to make a download... + +- Changed project title git + +- And some more corrections + +- More readme text + +- Added some short reference + +- some more md + +- Initial commit + diff --git a/packages/wekan-cfs-standard-packages/LICENSE.md b/packages/wekan-cfs-standard-packages/LICENSE.md new file mode 100644 index 000000000..e49ce11bc --- /dev/null +++ b/packages/wekan-cfs-standard-packages/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-standard-packages/README.md b/packages/wekan-cfs-standard-packages/README.md new file mode 100644 index 000000000..ce2fa69d7 --- /dev/null +++ b/packages/wekan-cfs-standard-packages/README.md @@ -0,0 +1,16 @@ +wekan-cfs-standard-packages +========================= + +This packege serves as a wrapper for several other packages and has no functionality in itself: + +* wekan-cfs-standard-packages (implies some component packages) + * wekan-cfs-base-package + * wekan-cfs-file + * wekan-cfs-collection + * wekan-cfs-collection-filters + * wekan-cfs-access-point + * wekan-cfs-worker + * wekan-cfs-upload-http + +Refer to the [CollectionFS](https://github.com/zcfs/Meteor-CollectionFS) +documentation for more information. diff --git a/packages/wekan-cfs-standard-packages/package.js b/packages/wekan-cfs-standard-packages/package.js new file mode 100644 index 000000000..cf1a8da05 --- /dev/null +++ b/packages/wekan-cfs-standard-packages/package.js @@ -0,0 +1,42 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-CollectionFS.git', + name: 'wekan-cfs-standard-packages', + version: '0.5.10', + summary: 'Filesystem for Meteor, collectionFS' +}); + +Package.onUse(function(api) { + // Rig the collectionFS package v2 + api.imply([ + // Base util rigs the basis for the FS scope and some general helper mehtods + 'wekan-cfs-base-package@0.0.30', + // Want to make use of the file object and its api, yes! + 'wekan-cfs-file@0.1.17', + // Add the FS.Collection to keep track of everything + 'wekan-cfs-collection@0.5.5', + // Support filters for easy rules about what may be inserted + 'wekan-cfs-collection-filters@0.2.4', + // Add the option to have ddp and http access point + 'wekan-cfs-access-point@0.1.49', + // We might also want to have the server create copies of our files? + 'wekan-cfs-worker@0.1.5', + // By default we want to support uploads over HTTP + 'wekan-cfs-upload-http@0.0.20', + ]); +}); + +Package.onTest(function (api) { + api.use('wekan-cfs-standard-packages'); + api.use('test-helpers@1.0.0', 'server'); + api.use([ + 'tinytest@1.0.0', + 'underscore@1.0.0', + 'ejson@1.0.0', + 'ordered-dict@1.0.0', + 'random@1.0.0', + 'tracker@1.0.3' + ]); + + api.addFiles('tests/server-tests.js', 'server'); + api.addFiles('tests/client-tests.js', 'client'); +}); diff --git a/packages/wekan-cfs-standard-packages/tests/client-tests.js b/packages/wekan-cfs-standard-packages/tests/client-tests.js new file mode 100644 index 000000000..08b9671ac --- /dev/null +++ b/packages/wekan-cfs-standard-packages/tests/client-tests.js @@ -0,0 +1,25 @@ +Tinytest.add('FS.Collection - client - test environment', function(test) { + test.isTrue(typeof FS.Utility !== 'undefined', 'test environment not initialized FS.Utility'); + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof FS.File !== 'undefined', 'test environment not initialized FS.File'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-standard-packages/tests/server-tests.js b/packages/wekan-cfs-standard-packages/tests/server-tests.js new file mode 100644 index 000000000..edf4c1624 --- /dev/null +++ b/packages/wekan-cfs-standard-packages/tests/server-tests.js @@ -0,0 +1,25 @@ +Tinytest.add('FS.Collection - server - test environment', function(test) { + test.isTrue(typeof FS.Utility !== 'undefined', 'test environment not initialized FS.Utility'); + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof FS.File !== 'undefined', 'test environment not initialized FS.File'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-storage-adapter/.travis.yml b/packages/wekan-cfs-storage-adapter/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-storage-adapter/LICENSE.md b/packages/wekan-cfs-storage-adapter/LICENSE.md new file mode 100644 index 000000000..51e60c3d0 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2015 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-storage-adapter/README.md b/packages/wekan-cfs-storage-adapter/README.md new file mode 100644 index 000000000..bc8e464a1 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/README.md @@ -0,0 +1,7 @@ +wekan-cfs-storage-adapter +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is used by other packages to create various storage adapters. diff --git a/.sandstorm-meteor-1.8/.meteor/cordova-plugins b/packages/wekan-cfs-storage-adapter/api.md similarity index 100% rename from .sandstorm-meteor-1.8/.meteor/cordova-plugins rename to packages/wekan-cfs-storage-adapter/api.md diff --git a/packages/wekan-cfs-storage-adapter/internal.api.md b/packages/wekan-cfs-storage-adapter/internal.api.md new file mode 100644 index 000000000..ae9f64ad3 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/internal.api.md @@ -0,0 +1,101 @@ +> File: ["storageAdapter.server.js"](storageAdapter.server.js) +> Where: {server} + +- +############################################################################# + +STORAGE ADAPTER + +############################################################################# + +#### *self*.insert(fsFile, [options], [callback])  Server #### +``` +Attempts to insert a file into the store, first running the beforeSave +function for the store if there is one. If there is a temporary failure, +returns (or passes to the second argument of the callback) `null`. If there +is a permanant failure or the beforeSave function returns `false`, returns +`false`. If the file is successfully stored, returns an object with file +info that the FS.Collection can save. +Also updates the `files` collection for this store to save info about this +file. +``` +- +*This method __insert__ is defined in `self`* + +__Arguments__ + +* __fsFile__ *{[FS.File](#FS.File)}* +The FS.File instance to be stored. +* __options__ *{Object}* (Optional) +Options (currently unused) +* __callback__ *{Function}* (Optional) +If not provided, will block and return file info. + +- + + + + +> ```self.insert = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:169](storageAdapter.server.js#L169) + +- + +#### *self*.update(fsFile, [options], [callback])  Server #### +``` +Attempts to update a file in the store, first running the beforeSave +function for the store if there is one. If there is a temporary failure, +returns (or passes to the second argument of the callback) `null`. If there +is a permanant failure or the beforeSave function returns `false`, returns +`false`. If the file is successfully stored, returns an object with file +info that the FS.Collection can save. +Also updates the `files` collection for this store to save info about this +file. +``` +- +*This method __update__ is defined in `self`* + +__Arguments__ + +* __fsFile__ *{[FS.File](#FS.File)}* +The FS.File instance to be stored. +* __options__ *{Object}* (Optional) +Options (currently unused) +* __callback__ *{Function}* (Optional) +If not provided, will block and return file info. + +- + + + + +> ```self.update = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:264](storageAdapter.server.js#L264) + +- + +#### *self*.remove(fsFile, [options], [callback])  Server #### +``` +Attempts to remove a file from the store. Returns true if removed, or false. +Also removes file info from the `files` collection for this store. +``` +- +*This method __remove__ is defined in `self`* + +__Arguments__ + +* __fsFile__ *{[FS.File](#FS.File)}* +The FS.File instance to be stored. +* __options__ *{Object}* (Optional) +Options + - __ignoreMissing__ *{Boolean}* (Optional) +Set true to treat missing files as a successful deletion. Otherwise throws an error. +* __callback__ *{Function}* (Optional) +If not provided, will block and return true or false + +- + + + + +> ```self.remove = function(fsFile, options, callback) { ...``` [storageAdapter.server.js:321](storageAdapter.server.js#L321) + +- diff --git a/packages/wekan-cfs-storage-adapter/package.js b/packages/wekan-cfs-storage-adapter/package.js new file mode 100644 index 000000000..92838d44a --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/package.js @@ -0,0 +1,50 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-cfs-storage-adapter.git', + name: 'wekan-cfs-storage-adapter', + version: '0.2.4', + summary: 'CollectionFS, Class for creating Storage adapters' +}); + +Npm.depends({ + 'length-stream': '0.1.1' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use([ + // CFS + 'wekan-cfs-base-package@0.0.30', + // Core + 'deps', + 'check', + 'livedata', + 'mongo-livedata', + 'ejson', + // Other + 'raix:eventemitter@0.1.1' + ]); + + // We want to make sure that its added to scope for now if installed. + // We have set a deprecation warning on the transform scope + api.use('wekan-cfs-graphicsmagick@0.0.17', 'server', { weak: true }); + + api.addFiles([ + 'storageAdapter.client.js' + ], 'client'); + + api.addFiles([ + 'storageAdapter.server.js', + 'transform.server.js' + ], 'server'); +}); + +Package.onTest(function (api) { + api.use('wekan-cfs-storage-adapter'); + api.use('test-helpers', 'server'); + api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', + 'random', 'deps']); + + api.addFiles('tests/server-tests.js', 'server'); + api.addFiles('tests/client-tests.js', 'client'); +}); diff --git a/packages/wekan-cfs-storage-adapter/storageAdapter.client.js b/packages/wekan-cfs-storage-adapter/storageAdapter.client.js new file mode 100644 index 000000000..90c87d00e --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/storageAdapter.client.js @@ -0,0 +1,37 @@ +/* global FS, _storageAdapters:true, EventEmitter */ + +// ############################################################################# +// +// STORAGE ADAPTER +// +// ############################################################################# + +_storageAdapters = {}; + +FS.StorageAdapter = function(name, options, api) { + var self = this; + + // Check the api + if (typeof api === 'undefined') { + throw new Error('FS.StorageAdapter please define an api'); + } + + // store reference for easy lookup by name + if (typeof _storageAdapters[name] !== 'undefined') { + throw new Error('Storage name already exists: "' + name + '"'); + } else { + _storageAdapters[name] = self; + } + + // extend self with options and other info + FS.Utility.extend(this, options || {}, { + name: name + }); + + // XXX: TODO, add upload feature here... + // we default to ddp upload but really let the SA like S3Cloud overwrite to + // implement direct client to s3 upload + +}; + +FS.StorageAdapter.prototype = new EventEmitter(); diff --git a/packages/wekan-cfs-storage-adapter/storageAdapter.server.js b/packages/wekan-cfs-storage-adapter/storageAdapter.server.js new file mode 100644 index 000000000..5af1bcbf5 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/storageAdapter.server.js @@ -0,0 +1,269 @@ +/* global FS, _storageAdapters:true, EventEmitter */ + +// ############################################################################# +// +// STORAGE ADAPTER +// +// ############################################################################# +_storageAdapters = {}; + +FS.StorageAdapter = function(storeName, options, api) { + var self = this, fileKeyMaker; + options = options || {}; + + // If storeName is the only argument, a string and the SA already found + // we will just return that SA + if (arguments.length === 1 && storeName === '' + storeName && + typeof _storageAdapters[storeName] !== 'undefined') + return _storageAdapters[storeName]; + + // Verify that the storage adapter defines all the necessary API methods + if (typeof api === 'undefined') { + throw new Error('FS.StorageAdapter please define an api'); + } + + FS.Utility.each('fileKey,remove,typeName,createReadStream,createWriteStream'.split(','), function(name) { + if (typeof api[name] === 'undefined') { + throw new Error('FS.StorageAdapter please define an api. "' + name + '" ' + (api.typeName || '')); + } + }); + + // Create an internal namespace, starting a name with underscore is only + // allowed for stores marked with options.internal === true + if (options.internal !== true && storeName[0] === '_') { + throw new Error('A storage adapter name may not begin with "_"'); + } + + if (storeName.indexOf('.') !== -1) { + throw new Error('A storage adapter name may not contain a "."'); + } + + // store reference for easy lookup by storeName + if (typeof _storageAdapters[storeName] !== 'undefined') { + throw new Error('Storage name already exists: "' + storeName + '"'); + } else { + _storageAdapters[storeName] = self; + } + + // User can customize the file key generation function + if (typeof options.fileKeyMaker === "function") { + fileKeyMaker = options.fileKeyMaker; + } else { + fileKeyMaker = api.fileKey; + } + + // User can provide a function to adjust the fileObj + // before it is written to the store. + var beforeWrite = options.beforeWrite; + + // extend self with options and other info + FS.Utility.extend(this, options, { + name: storeName, + typeName: api.typeName + }); + + // Create a nicer abstracted adapter interface + self.adapter = {}; + + self.adapter.fileKey = function(fileObj) { + return fileKeyMaker(fileObj); + }; + + // Return readable stream for fileKey + self.adapter.createReadStreamForFileKey = function(fileKey, options) { + if (FS.debug) console.log('createReadStreamForFileKey ' + storeName); + return FS.Utility.safeStream( api.createReadStream(fileKey, options) ); + }; + + // Return readable stream for fileObj + self.adapter.createReadStream = function(fileObj, options) { + if (FS.debug) console.log('createReadStream ' + storeName); + if (self.internal) { + // Internal stores take a fileKey + return self.adapter.createReadStreamForFileKey(fileObj, options); + } + return FS.Utility.safeStream( self._transform.createReadStream(fileObj, options) ); + }; + + function logEventsForStream(stream) { + if (FS.debug) { + stream.on('stored', function() { + console.log('-----------STORED STREAM', storeName); + }); + + stream.on('close', function() { + console.log('-----------CLOSE STREAM', storeName); + }); + + stream.on('end', function() { + console.log('-----------END STREAM', storeName); + }); + + stream.on('finish', function() { + console.log('-----------FINISH STREAM', storeName); + }); + + stream.on('error', function(error) { + console.log('-----------ERROR STREAM', storeName, error && (error.message || error.code)); + }); + } + } + + // Return writeable stream for fileKey + self.adapter.createWriteStreamForFileKey = function(fileKey, options) { + if (FS.debug) console.log('createWriteStreamForFileKey ' + storeName); + var writeStream = FS.Utility.safeStream( api.createWriteStream(fileKey, options) ); + + logEventsForStream(writeStream); + + return writeStream; + }; + + // Return writeable stream for fileObj + self.adapter.createWriteStream = function(fileObj, options) { + if (FS.debug) console.log('createWriteStream ' + storeName + ', internal: ' + !!self.internal); + + if (self.internal) { + // Internal stores take a fileKey + return self.adapter.createWriteStreamForFileKey(fileObj, options); + } + + // If we haven't set name, type, or size for this version yet, + // set it to same values as original version. We don't save + // these to the DB right away because they might be changed + // in a transformWrite function. + if (!fileObj.name({store: storeName})) { + fileObj.name(fileObj.name(), {store: storeName, save: false}); + } + if (!fileObj.type({store: storeName})) { + fileObj.type(fileObj.type(), {store: storeName, save: false}); + } + if (!fileObj.size({store: storeName})) { + fileObj.size(fileObj.size(), {store: storeName, save: false}); + } + + // Call user function to adjust file metadata for this store. + // We support updating name, extension, and/or type based on + // info returned in an object. Or `fileObj` could be + // altered directly within the beforeWrite function. + if (beforeWrite) { + var fileChanges = beforeWrite(fileObj); + if (typeof fileChanges === "object") { + if (fileChanges.extension) { + fileObj.extension(fileChanges.extension, {store: storeName, save: false}); + } else if (fileChanges.name) { + fileObj.name(fileChanges.name, {store: storeName, save: false}); + } + if (fileChanges.type) { + fileObj.type(fileChanges.type, {store: storeName, save: false}); + } + } + } + + var writeStream = FS.Utility.safeStream( self._transform.createWriteStream(fileObj, options) ); + + logEventsForStream(writeStream); + + // Its really only the storage adapter who knows if the file is uploaded + // + // We have to use our own event making sure the storage process is completed + // this is mainly + writeStream.safeOn('stored', function(result) { + if (typeof result.fileKey === 'undefined') { + throw new Error('SA ' + storeName + ' type ' + api.typeName + ' did not return a fileKey'); + } + if (FS.debug) console.log('SA', storeName, 'stored', result.fileKey); + // Set the fileKey + fileObj.copies[storeName].key = result.fileKey; + + // Update the size, as provided by the SA, in case it was changed by stream transformation + if (typeof result.size === "number") { + fileObj.copies[storeName].size = result.size; + } + + // Set last updated time, either provided by SA or now + fileObj.copies[storeName].updatedAt = result.storedAt || new Date(); + + // If the file object copy havent got a createdAt then set this + if (typeof fileObj.copies[storeName].createdAt === 'undefined') { + fileObj.copies[storeName].createdAt = fileObj.copies[storeName].updatedAt; + } + + fileObj._saveChanges(storeName); + + // There is code in transform that may have set the original file size, too. + fileObj._saveChanges('_original'); + }); + + // Emit events from SA + writeStream.once('stored', function(/*result*/) { + // XXX Because of the way stores inherit from SA, this will emit on every store. + // Maybe need to rewrite the way we inherit from SA? + var emitted = self.emit('stored', storeName, fileObj); + if (FS.debug && !emitted) { + console.log(fileObj.name() + ' was successfully stored in the ' + storeName + ' store. You are seeing this informational message because you enabled debugging and you have not defined any listeners for the "stored" event on this store.'); + } + }); + + writeStream.on('error', function(error) { + // XXX We could wrap and clarify error + // XXX Because of the way stores inherit from SA, this will emit on every store. + // Maybe need to rewrite the way we inherit from SA? + var emitted = self.emit('error', storeName, error, fileObj); + if (FS.debug && !emitted) { + console.log(error); + } + }); + + return writeStream; + }; + + //internal + self._removeAsync = function(fileKey, callback) { + // Remove the file from the store + api.remove.call(self, fileKey, callback); + }; + + /** + * @method FS.StorageAdapter.prototype.remove + * @public + * @param {FS.File} fsFile The FS.File instance to be stored. + * @param {Function} [callback] If not provided, will block and return true or false + * + * Attempts to remove a file from the store. Returns true if removed or not + * found, or false if the file couldn't be removed. + */ + self.adapter.remove = function(fileObj, callback) { + if (FS.debug) console.log("---SA REMOVE"); + + // Get the fileKey + var fileKey = (fileObj instanceof FS.File) ? self.adapter.fileKey(fileObj) : fileObj; + + if (callback) { + return self._removeAsync(fileKey, FS.Utility.safeCallback(callback)); + } else { + return Meteor.wrapAsync(self._removeAsync)(fileKey); + } + }; + + self.remove = function(fileObj, callback) { + // Add deprecation note + console.warn('Storage.remove is deprecating, use "Storage.adapter.remove"'); + return self.adapter.remove(fileObj, callback); + }; + + if (typeof api.init === 'function') { + Meteor.wrapAsync(api.init.bind(self))(); + } + + // This supports optional transformWrite and transformRead + self._transform = new FS.Transform({ + adapter: self.adapter, + // Optional transformation functions: + transformWrite: options.transformWrite, + transformRead: options.transformRead + }); + +}; + +Npm.require('util').inherits(FS.StorageAdapter, EventEmitter); diff --git a/packages/wekan-cfs-storage-adapter/tests/client-tests.js b/packages/wekan-cfs-storage-adapter/tests/client-tests.js new file mode 100644 index 000000000..efe5a3abb --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/tests/client-tests.js @@ -0,0 +1,44 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-storage-adapter - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Client Tests + * + * construct FS.File with no arguments + * construct FS.File passing in File + * construct FS.File passing in Blob + * load blob into FS.File and then call FS.File.toDataUrl + * call FS.File.setDataFromBinary, then FS.File.getBlob(); make sure correct data is returned + * load blob into FS.File and then call FS.File.getBinary() with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * load blob into FS.File and make sure FS.File.saveLocal initiates a download (possibly can't do automatically) + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-storage-adapter/tests/server-tests.js b/packages/wekan-cfs-storage-adapter/tests/server-tests.js new file mode 100644 index 000000000..b25036569 --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/tests/server-tests.js @@ -0,0 +1,49 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-storage-adapter - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Server Tests + * + * construct FS.File with no arguments + * load data with FS.File.setDataFromBuffer + * load data with FS.File.setDataFromBinary + * load data and then call FS.File.toDataUrl with and without callback + * load buffer into FS.File and then call FS.File.getBinary with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * (call these with and without callback to test sync vs. async) + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * + * + * FS.Collection Server Tests + * + * Make sure options.filter is respected + * + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-storage-adapter/transform.server.js b/packages/wekan-cfs-storage-adapter/transform.server.js new file mode 100644 index 000000000..8936ddc3f --- /dev/null +++ b/packages/wekan-cfs-storage-adapter/transform.server.js @@ -0,0 +1,119 @@ +/* global FS */ + +var PassThrough = Npm.require('stream').PassThrough; +var lengthStream = Npm.require('length-stream'); + +FS.Transform = function(options) { + var self = this; + + options = options || {}; + + if (!(self instanceof FS.Transform)) + throw new Error('FS.Transform must be called with the "new" keyword'); + + if (!options.adapter) + throw new Error('Transform expects option.adapter to be a storage adapter'); + + self.storage = options.adapter; + + // Fetch the transformation functions if any + self.transformWrite = options.transformWrite; + self.transformRead = options.transformRead; +}; + +// Allow packages to add scope +FS.Transform.scope = {}; + +// The transformation stream triggers an "stored" event when data is stored into +// the storage adapter +FS.Transform.prototype.createWriteStream = function(fileObj) { + var self = this; + + // Get the file key + var fileKey = self.storage.fileKey(fileObj); + + // Rig write stream + var destinationStream = self.storage.createWriteStreamForFileKey(fileKey, { + // Not all SA's can set these options and cfs dont depend on setting these + // but its nice if other systems are accessing the SA that some of the data + // is also available to those + aliases: [fileObj.name()], + contentType: fileObj.type(), + metadata: fileObj.metadata + }); + + // Pass through transformWrite function if provided + if (typeof self.transformWrite === 'function') { + + destinationStream = addPassThrough(destinationStream, function (ptStream, originalStream) { + // Rig transform + try { + self.transformWrite.call(FS.Transform.scope, fileObj, ptStream, originalStream); + // XXX: If the transform function returns a buffer should we stream that? + } catch(err) { + // We emit an error - should we throw an error? + console.warn('FS.Transform.createWriteStream transform function failed, Error: '); + throw err; + } + }); + + } + + // If original doesn't have size, add another PassThrough to get and set the size. + // This will run on size=0, too, which is OK. + // NOTE: This must come AFTER the transformWrite code block above. This might seem + // confusing, but by coming after it, this will actually be executed BEFORE the user's + // transform, which is what we need in order to be sure we get the original file + // size and not the transformed file size. + if (!fileObj.size()) { + destinationStream = addPassThrough(destinationStream, function (ptStream, originalStream) { + var lstream = lengthStream(function (fileSize) { + fileObj.size(fileSize, {save: false}); + }); + + ptStream.pipe(lstream).pipe(originalStream); + }); + } + + return destinationStream; +}; + +FS.Transform.prototype.createReadStream = function(fileObj, options) { + var self = this; + + // Get the file key + var fileKey = self.storage.fileKey(fileObj); + + // Rig read stream + var sourceStream = self.storage.createReadStreamForFileKey(fileKey, options); + + // Pass through transformRead function if provided + if (typeof self.transformRead === 'function') { + + sourceStream = addPassThrough(sourceStream, function (ptStream, originalStream) { + // Rig transform + try { + self.transformRead.call(FS.Transform.scope, fileObj, originalStream, ptStream); + } catch(err) { + //throw new Error(err); + // We emit an error - should we throw an error? + sourceStream.emit('error', 'FS.Transform.createReadStream transform function failed'); + } + }); + + } + + // We dont transform just normal SA interface + return sourceStream; +}; + +// Utility function to simplify adding layers of passthrough +function addPassThrough(stream, func) { + var pts = new PassThrough(); + // We pass on the special "stored" event for those listening + stream.on('stored', function(result) { + pts.emit('stored', result); + }); + func(pts, stream); + return pts; +} diff --git a/packages/wekan-cfs-temp-store/.travis.yml b/packages/wekan-cfs-temp-store/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-temp-store/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-temp-store/CHANGELOG.md b/packages/wekan-cfs-temp-store/CHANGELOG.md new file mode 100644 index 000000000..3113b8e61 --- /dev/null +++ b/packages/wekan-cfs-temp-store/CHANGELOG.md @@ -0,0 +1,169 @@ +# Changelog + +## vCurrent +## [v0.1.2] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.1.2) +#### 17/12/14 by Morten Henriksen +## [v0.1.1] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.1.1) +#### 17/12/14 by Morten Henriksen +- mbr update, remove versions.json + +- Bump to version 0.1.1 + +## [v0.1.0] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.1.0) +#### 17/12/14 by Morten Henriksen +- mbr update versions and fix warnings + +- fix 0.9.1 package scope + +- don't rely on package names; fix for 0.9.1 + +- 0.9.1 support + +## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.29) +#### 28/08/14 by Morten Henriksen +- Meteor Package System Update + +## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.28) +#### 27/08/14 by Eric Dobbertin +- change package name to lowercase + +## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.27) +#### 17/06/14 by Eric Dobbertin +- add `FS.TempStore.removeAll` method + +## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.26) +#### 30/04/14 by Eric Dobbertin +## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.25) +#### 30/04/14 by Eric Dobbertin +- use third-party combined-stream node pkg as attempt to resolve pesky streaming issues + +## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.24) +#### 29/04/14 by Eric Dobbertin +- generate api docs + +- fileKey methods now expect an FS.File always, so we give them one + +- small FS.File API change + +## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.23) +#### 12/04/14 by Eric Dobbertin +- test for packages since we're assigning default error functions for stores + +## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.22) +#### 12/04/14 by Eric Dobbertin +- avoid errors if file already removed from temp store + +## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.21) +#### 12/04/14 by Eric Dobbertin +## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.20) +#### 08/04/14 by Eric Dobbertin +- cleanup stored/uploaded events and further improve chunk tracking + +## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.19) +#### 08/04/14 by Eric Dobbertin +- use internal tracking collection + +- Have TempStore set the size + +- Add the SA on stored result + +- allow unset chunkSum + +## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.18) +#### 06/04/14 by Eric Dobbertin +- delete chunkCount and chunkSize props from fileObj after upload is complete + +## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.17) +#### 06/04/14 by Eric Dobbertin +- We now wait to mount storage until it's needed (first upload begins); this ensures that we are able to accurately check for the cfs-worker package, which loads after this one. It also makes the code a bit cleaner. + +## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.16) +#### 04/04/14 by Morten Henriksen +- Temporary workaround: We currently we generate a mongoId if gridFS is used for TempStore + +- Note: At the moment tempStore will only use gridfs if no filesystem is installed + +## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.15) +#### 02/04/14 by Morten Henriksen +- Use the stored event and object instead (result object is not used at the moment - but we could store an id at some point) + +## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.14) +#### 31/03/14 by Eric Dobbertin +- use latest releases + +- use latest releases + +## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.13) +#### 31/03/14 by Morten Henriksen +- Try to use latest when using weak deps + +## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.12) +#### 30/03/14 by Morten Henriksen +## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.11) +#### 30/03/14 by Morten Henriksen +- Set noon callback - we just want the file gone + +## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.10) +#### 29/03/14 by Morten Henriksen +- add filesystem and gridfs as weak deps + +## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.9) +#### 29/03/14 by Morten Henriksen +- Add check to see if FS.TempStore.Storage is set + +## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.8) +#### 29/03/14 by Morten Henriksen +- Converting TempStore to use SA api + +## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.7) +#### 25/03/14 by Morten Henriksen +- use `new Date` + +- Have TempStore emit relevant events + +## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.6) +#### 23/03/14 by Morten Henriksen +- Rollback to specific git dependency + +- use collectionFS travis version force update + +## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.5) +#### 22/03/14 by Morten Henriksen +- try to fix travis test by using general package references + +## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.4) +#### 21/03/14 by Morten Henriksen +- fix chunk files not actually being deleted + +## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.3) +#### 18/03/14 by Morten Henriksen +- * TempStore is now an EventEmitter * progress event * uploaded * (start) should perhaps be created * remove * Added FS.TempStore.listParts - will return lookup object listing the parts already uploaded + +- Allow chunk to be undefined an thereby have the createWriteStream follow normal streaming api + +- Allow undefined in chunkPath + +- added comments + +- bug hunting + +- Add streaming WIP + +- rename temp store collection to 'cfs.tempstore' + +- fix ensureForFile + +- track tempstore chunks in our own collection rather than in the file object + +- change to accept buffer; less converting + +- prevent bytesUploaded from getting bigger than size + +## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.2) +#### 15/02/14 by Morten Henriksen +- fix typo + +## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-tempstore/tree/v0.0.1) +#### 13/02/14 by Morten Henriksen +- init commit + diff --git a/packages/wekan-cfs-temp-store/LICENSE.md b/packages/wekan-cfs-temp-store/LICENSE.md new file mode 100644 index 000000000..1a3820821 --- /dev/null +++ b/packages/wekan-cfs-temp-store/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-temp-store/README.md b/packages/wekan-cfs-temp-store/README.md new file mode 100644 index 000000000..e0d8f0c48 --- /dev/null +++ b/packages/wekan-cfs-temp-store/README.md @@ -0,0 +1,24 @@ +wekan-cfs-tempstore +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). It provides +an API for quickly storing chunks of file data in temporary files. If also supports deleting those chunks, and combining them into one +binary object and attaching it to an FS.File instance. + +You don't need to manually add this package to your app, but you could replace +this package with your own if you want to handle temporary storage in another +way. + +> `FS.TempStore` uses the `wekan-cfs-storage-adapter` compatible Storage Adapters, both `FS.Store.FileSystem` and `FS.Store.GridFS` will be defaulted. *for more information read the [internal.api.md](internal.api.md)* + +##Documentation +[API Documentation](api.md) + +##Contribute +Here's the [complete API documentation](internal.api.md), including private methods. + +Update docs, `npm install docmeteor` +```bash +$ docmeteor +``` \ No newline at end of file diff --git a/packages/wekan-cfs-temp-store/api.md b/packages/wekan-cfs-temp-store/api.md new file mode 100644 index 000000000..cbb34632e --- /dev/null +++ b/packages/wekan-cfs-temp-store/api.md @@ -0,0 +1,112 @@ +## cfs-tempstore Public API ## + +CollectionFS, temporary storage + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +##Temporary Storage + +Temporary storage is used for chunked uploads until all chunks are received +and all copies have been made or given up. In some cases, the original file +is stored only in temporary storage (for example, if all copies do some +manipulation in beforeSave). This is why we use the temporary file as the +basis for each saved copy, and then remove it after all copies are saved. + +Every chunk is saved as an individual temporary file. This is safer than +attempting to write multiple incoming chunks to different positions in a +single temporary file, which can lead to write conflicts. + +Using temp files also allows us to easily resume uploads, even if the server +restarts, and to keep the working memory clear. +The FS.TempStore emits events that others are able to listen to +- + +### *fs*.TempStore {object}  Server ### + +*This property __TempStore__ is defined in `FS`* +it's an event emitter* + +> ```FS.TempStore = new EventEmitter();``` [tempStore.js:28](tempStore.js#L28) + + + +- +We will not mount a storage adapter until needed. This allows us to check for the +existance of FS.FileWorker, which is loaded after this package because it +depends on this package. + +- +XXX: TODO +FS.TempStore.on('stored', function(fileObj, chunkCount, result) { +This should work if we pass on result from the SA on stored event... +fileObj.update({ $set: { chunkSum: 1, chunkCount: chunkCount, size: result.size } }); +}); +Stream implementation +- + +### *fsTempstore*.removeFile(fileObj)  Server ### + +*This method __removeFile__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + +This function removes the file from tempstorage - it cares not if file is +already removed or not found, goal is reached anyway. + +> ```FS.TempStore.removeFile = function(fileObj) { ...``` [tempStore.js:169](tempStore.js#L169) + + +- + +### *fsTempstore*.createWriteStream(fileObj, [options])  Server ### + +*This method __createWriteStream__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + + File to store in temporary storage + +* __options__ *{[Number ](#Number )|[ String](# String)}* (Optional) + +__Returns__ *{Stream}* +Writeable stream + + +`options` of different types mean differnt things: +`undefined` We store the file in one part +(Normal server-side api usage)* +`Number` the number is the part number total +(multipart uploads will use this api)* +`String` the string is the name of the `store` that wants to store file data +(stores that want to sync their data to the rest of the files stores will use this)* + +> Note: fileObj must be mounted on a `FS.Collection`, it makes no sense to store otherwise + +> ```FS.TempStore.createWriteStream = function(fileObj, options) { ...``` [tempStore.js:217](tempStore.js#L217) + + +- + +### *fsTempstore*.createReadStream(fileObj)  Server ### + +*This method __createReadStream__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + + The file to read + + +__Returns__ *{Stream}* +Returns readable stream + + + +> ```FS.TempStore.createReadStream = function(fileObj) { ...``` [tempStore.js:313](tempStore.js#L313) + + diff --git a/packages/wekan-cfs-temp-store/internal.api.md b/packages/wekan-cfs-temp-store/internal.api.md new file mode 100644 index 000000000..4b6e1f6d5 --- /dev/null +++ b/packages/wekan-cfs-temp-store/internal.api.md @@ -0,0 +1,225 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["tempStore.js"](tempStore.js) Where: {server}__ + +*** + +##Temporary Storage + +Temporary storage is used for chunked uploads until all chunks are received +and all copies have been made or given up. In some cases, the original file +is stored only in temporary storage (for example, if all copies do some +manipulation in beforeSave). This is why we use the temporary file as the +basis for each saved copy, and then remove it after all copies are saved. + +Every chunk is saved as an individual temporary file. This is safer than +attempting to write multiple incoming chunks to different positions in a +single temporary file, which can lead to write conflicts. + +Using temp files also allows us to easily resume uploads, even if the server +restarts, and to keep the working memory clear. +The FS.TempStore emits events that others are able to listen to +- + +### *fs*.TempStore {object}  Server ### + +*This property __TempStore__ is defined in `FS`* +it's an event emitter* + +> ```FS.TempStore = new EventEmitter();``` [tempStore.js:28](tempStore.js#L28) + + +- + +### *fsTempstore*.Storage {StorageAdapter}  Server ### + +*This property is private* +*This property __Storage__ is defined in `FS.TempStore`* + +This property is set to either `FS.Store.FileSystem` or `FS.Store.GridFS` + +__When and why:__ +We normally default to `cfs-filesystem` unless its not installed. *(we default to gridfs if installed)* +But if `cfs-gridfs` and `cfs-worker` is installed we default to `cfs-gridfs` + +If `cfs-gridfs` and `cfs-filesystem` is not installed we log a warning. +the user can set `FS.TempStore.Storage` them selfs eg.: +```js +// Its important to set `internal: true` this lets the SA know that we +// are using this internally and it will give us direct SA api +FS.TempStore.Storage = new FS.Store.GridFS('_tempstore', { internal: true }); +``` + +> Note: This is considered as `advanced` use, its not a common pattern. + +> ```FS.TempStore.Storage = null;``` [tempStore.js:54](tempStore.js#L54) + + + +- +We will not mount a storage adapter until needed. This allows us to check for the +existance of FS.FileWorker, which is loaded after this package because it +depends on this package. + +- +XXX: TODO +FS.TempStore.on('stored', function(fileObj, chunkCount, result) { +This should work if we pass on result from the SA on stored event... +fileObj.update({ $set: { chunkSum: 1, chunkCount: chunkCount, size: result.size } }); +}); +Stream implementation +- + +### _chunkPath([n])  Server ### + +*This method is private* + +__Arguments__ + +* __n__ *{Number}* (Optional) + + Chunk number + + +__Returns__ *{String}* +Chunk naming convention + + +> ```_chunkPath = function(n) { ...``` [tempStore.js:104](tempStore.js#L104) + + +- + +### _fileReference(fileObj, chunk)  Server ### + +*This method is private* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* +* __chunk__ *{Number}* + +__Returns__ *{String}* +Generated SA specific fileKey for the chunk + + +Note: Calling function should call mountStorage() first, and +make sure that fileObj is mounted. + +> ```_fileReference = function(fileObj, chunk, existing) { ...``` [tempStore.js:118](tempStore.js#L118) + + +- + +### *fsTempstore*.exists(File)  Server ### + +*This method __exists__ is defined in `FS.TempStore`* + +__Arguments__ + +* __File__ *{[FS.File](#FS.File)}* + + object + + +__Returns__ *{Boolean}* +Is this file, or parts of it, currently stored in the TempStore + + +> ```FS.TempStore.exists = function(fileObj) { ...``` [tempStore.js:145](tempStore.js#L145) + + +- + +### *fsTempstore*.listParts(fileObj)  Server ### + +*This method __listParts__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + +__Returns__ *{Object}* +of parts already stored + +__TODO__ +``` +* This is not yet implemented, milestone 1.1.0 +``` + + +> ```FS.TempStore.listParts = function(fileObj) { ...``` [tempStore.js:156](tempStore.js#L156) + + +- + +### *fsTempstore*.removeFile(fileObj)  Server ### + +*This method __removeFile__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + +This function removes the file from tempstorage - it cares not if file is +already removed or not found, goal is reached anyway. + +> ```FS.TempStore.removeFile = function(fileObj) { ...``` [tempStore.js:169](tempStore.js#L169) + + +- + +### *fsTempstore*.createWriteStream(fileObj, [options])  Server ### + +*This method __createWriteStream__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + + File to store in temporary storage + +* __options__ *{[Number ](#Number )|[ String](# String)}* (Optional) + +__Returns__ *{Stream}* +Writeable stream + + +`options` of different types mean differnt things: +`undefined` We store the file in one part +(Normal server-side api usage)* +`Number` the number is the part number total +(multipart uploads will use this api)* +`String` the string is the name of the `store` that wants to store file data +(stores that want to sync their data to the rest of the files stores will use this)* + +> Note: fileObj must be mounted on a `FS.Collection`, it makes no sense to store otherwise + +> ```FS.TempStore.createWriteStream = function(fileObj, options) { ...``` [tempStore.js:217](tempStore.js#L217) + + +- + +### *fsTempstore*.createReadStream(fileObj)  Server ### + +*This method __createReadStream__ is defined in `FS.TempStore`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + + The file to read + + +__Returns__ *{Stream}* +Returns readable stream + + + +> ```FS.TempStore.createReadStream = function(fileObj) { ...``` [tempStore.js:313](tempStore.js#L313) + + diff --git a/packages/wekan-cfs-temp-store/package.js b/packages/wekan-cfs-temp-store/package.js new file mode 100644 index 000000000..61487fed9 --- /dev/null +++ b/packages/wekan-cfs-temp-store/package.js @@ -0,0 +1,32 @@ + Package.describe({ + git: 'https://github.com/zcfs/Meteor-cfs-tempstore.git', + name: 'wekan-cfs-tempstore', + version: '0.1.6', + summary: 'CollectionFS, temporary storage' +}); + +Npm.depends({ + 'combined-stream': '0.0.4' +}); + +Package.onUse(function(api) { + api.use(['wekan-cfs-base-package@0.0.30', 'wekan-cfs-file@0.1.16', 'ecmascript@0.1.0']); + + api.use('wekan-cfs-filesystem@0.1.2', { weak: true }); + api.use('wekan-cfs-gridfs@0.0.30', { weak: true }); + + api.use('mongo@1.0.0'); + + api.addFiles([ + 'tempStore.js' + ], 'server'); +}); + +// Package.on_test(function (api) { +// api.use('collectionfs'); +// api.use('test-helpers', 'server'); +// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', +// 'random', 'deps']); + +// api.addFiles('tests/server-tests.js', 'server'); +// }); diff --git a/packages/wekan-cfs-temp-store/tempStore.js b/packages/wekan-cfs-temp-store/tempStore.js new file mode 100644 index 000000000..3823737f4 --- /dev/null +++ b/packages/wekan-cfs-temp-store/tempStore.js @@ -0,0 +1,395 @@ +// ##Temporary Storage +// +// Temporary storage is used for chunked uploads until all chunks are received +// and all copies have been made or given up. In some cases, the original file +// is stored only in temporary storage (for example, if all copies do some +// manipulation in beforeSave). This is why we use the temporary file as the +// basis for each saved copy, and then remove it after all copies are saved. +// +// Every chunk is saved as an individual temporary file. This is safer than +// attempting to write multiple incoming chunks to different positions in a +// single temporary file, which can lead to write conflicts. +// +// Using temp files also allows us to easily resume uploads, even if the server +// restarts, and to keep the working memory clear. + +// The FS.TempStore emits events that others are able to listen to +var EventEmitter = Npm.require('events').EventEmitter; + +// We have a special stream concating all chunk files into one readable stream +var CombinedStream = Npm.require('combined-stream'); + +/** @namespace FS.TempStore + * @property FS.TempStore + * @type {object} + * @public + * @summary An event emitter + */ +FS.TempStore = new EventEmitter(); + +// Create a tracker collection for keeping track of all chunks for any files that are currently in the temp store +var tracker = FS.TempStore.Tracker = new Mongo.Collection('cfs._tempstore.chunks'); + +/** + * @property FS.TempStore.Storage + * @type {StorageAdapter} + * @namespace FS.TempStore + * @private + * @summary This property is set to either `FS.Store.FileSystem` or `FS.Store.GridFS` + * + * __When and why:__ + * We normally default to `cfs-filesystem` unless its not installed. *(we default to gridfs if installed)* + * But if `cfs-gridfs` and `cfs-worker` is installed we default to `cfs-gridfs` + * + * If `cfs-gridfs` and `cfs-filesystem` is not installed we log a warning. + * the user can set `FS.TempStore.Storage` them selfs eg.: + * ```js + * // Its important to set `internal: true` this lets the SA know that we + * // are using this internally and it will give us direct SA api + * FS.TempStore.Storage = new FS.Store.GridFS('_tempstore', { internal: true }); + * ``` + * + * > Note: This is considered as `advanced` use, its not a common pattern. + */ +FS.TempStore.Storage = null; + +// We will not mount a storage adapter until needed. This allows us to check for the +// existance of FS.FileWorker, which is loaded after this package because it +// depends on this package. +function mountStorage() { + + if (FS.TempStore.Storage) return; + + // XXX: We could replace this test, testing the FS scope for grifFS etc. + // This is on the todo later when we get "stable" + if (Package["wekan-cfs-gridfs"] && (Package["wekan-cfs-worker"] || !Package["wekan-cfs-filesystem"])) { + // If the file worker is installed we would prefer to use the gridfs sa + // for scalability. We also default to gridfs if filesystem is not found + + // Use the gridfs + FS.TempStore.Storage = new FS.Store.GridFS('_tempstore', { internal: true }); + } else if (Package["wekan-cfs-filesystem"]) { + + // use the Filesystem + FS.TempStore.Storage = new FS.Store.FileSystem('_tempstore', { internal: true }); + } else { + throw new Error('FS.TempStore.Storage is not set: Install wekan-cfs-filesystem or wekan-cfs-gridfs or set it manually'); + } + + FS.debug && console.log('TempStore is mounted on', FS.TempStore.Storage.typeName); +} + +function mountFile(fileObj, name) { + if (!fileObj.isMounted()) { + throw new Error(name + ' cannot work with unmounted file'); + } +} + +// We update the fileObj on progress +FS.TempStore.on('progress', function(fileObj, chunkNum, count, total, result) { + FS.debug && console.log('TempStore progress: Received ' + count + ' of ' + total + ' chunks for ' + fileObj.name()); +}); + +// XXX: TODO +// FS.TempStore.on('stored', function(fileObj, chunkCount, result) { +// // This should work if we pass on result from the SA on stored event... +// fileObj.update({ $set: { chunkSum: 1, chunkCount: chunkCount, size: result.size } }); +// }); + +// Stream implementation + +/** + * @method _chunkPath + * @private + * @param {Number} [n] Chunk number + * @returns {String} Chunk naming convention + */ +_chunkPath = function(n) { + return (n || 0) + '.chunk'; +}; + +/** + * @method _fileReference + * @param {FS.File} fileObj + * @param {Number} chunk + * @private + * @returns {String} Generated SA specific fileKey for the chunk + * + * Note: Calling function should call mountStorage() first, and + * make sure that fileObj is mounted. + */ +_fileReference = function(fileObj, chunk, existing) { + // Maybe it's a chunk we've already saved + existing = existing || tracker.findOne({fileId: fileObj._id, collectionName: fileObj.collectionName}); + + // Make a temporary fileObj just for fileKey generation + var tempFileObj = new FS.File({ + collectionName: fileObj.collectionName, + _id: fileObj._id, + original: { + name: _chunkPath(chunk) + }, + copies: { + _tempstore: { + key: existing && existing.keys[chunk] + } + } + }); + + // Return a fitting fileKey SA specific + return FS.TempStore.Storage.adapter.fileKey(tempFileObj); +}; + +/** + * @method FS.TempStore.exists + * @param {FS.File} File object + * @returns {Boolean} Is this file, or parts of it, currently stored in the TempStore + */ +FS.TempStore.exists = function(fileObj) { + var existing = tracker.findOne({fileId: fileObj._id, collectionName: fileObj.collectionName}); + return !!existing; +}; + +/** + * @method FS.TempStore.listParts + * @param {FS.File} fileObj + * @returns {Object} of parts already stored + * @todo This is not yet implemented, milestone 1.1.0 + */ +FS.TempStore.listParts = function fsTempStoreListParts(fileObj) { + var self = this; + console.warn('This function is not correctly implemented using SA in TempStore'); + //XXX This function might be necessary for resume. Not currently supported. +}; + +/** + * @method FS.TempStore.removeFile + * @public + * @param {FS.File} fileObj + * This function removes the file from tempstorage - it cares not if file is + * already removed or not found, goal is reached anyway. + */ +FS.TempStore.removeFile = function fsTempStoreRemoveFile(fileObj) { + var self = this; + + // Ensure that we have a storage adapter mounted; if not, throw an error. + mountStorage(); + + // If fileObj is not mounted or can't be, throw an error + mountFile(fileObj, 'FS.TempStore.removeFile'); + + // Emit event + self.emit('remove', fileObj); + + var chunkInfo = tracker.findOne({ + fileId: fileObj._id, + collectionName: fileObj.collectionName + }); + + if (chunkInfo) { + + // Unlink each file + FS.Utility.each(chunkInfo.keys || {}, function (key, chunk) { + var fileKey = _fileReference(fileObj, chunk, chunkInfo); + FS.TempStore.Storage.adapter.remove(fileKey, FS.Utility.noop); + }); + + // Remove fileObj from tracker collection, too + tracker.remove({_id: chunkInfo._id}); + + } +}; + +/** + * @method FS.TempStore.removeAll + * @public + * @summary This function removes all files from tempstorage - it cares not if file is + * already removed or not found, goal is reached anyway. + */ +FS.TempStore.removeAll = function fsTempStoreRemoveAll() { + var self = this; + + // Ensure that we have a storage adapter mounted; if not, throw an error. + mountStorage(); + + tracker.find().forEach(function (chunkInfo) { + // Unlink each file + FS.Utility.each(chunkInfo.keys || {}, function (key, chunk) { + var fileKey = _fileReference({_id: chunkInfo.fileId, collectionName: chunkInfo.collectionName}, chunk, chunkInfo); + FS.TempStore.Storage.adapter.remove(fileKey, FS.Utility.noop); + }); + + // Remove from tracker collection, too + tracker.remove({_id: chunkInfo._id}); + }); +}; + +/** + * @method FS.TempStore.createWriteStream + * @public + * @param {FS.File} fileObj File to store in temporary storage + * @param {Number | String} [options] + * @returns {Stream} Writeable stream + * + * `options` of different types mean differnt things: + * * `undefined` We store the file in one part + * *(Normal server-side api usage)* + * * `Number` the number is the part number total + * *(multipart uploads will use this api)* + * * `String` the string is the name of the `store` that wants to store file data + * *(stores that want to sync their data to the rest of the files stores will use this)* + * + * > Note: fileObj must be mounted on a `FS.Collection`, it makes no sense to store otherwise + */ +FS.TempStore.createWriteStream = function(fileObj, options) { + var self = this; + + // Ensure that we have a storage adapter mounted; if not, throw an error. + mountStorage(); + + // If fileObj is not mounted or can't be, throw an error + mountFile(fileObj, 'FS.TempStore.createWriteStream'); + + // Cache the selector for use multiple times below + var selector = {fileId: fileObj._id, collectionName: fileObj.collectionName}; + + // TODO, should pass in chunkSum so we don't need to use FS.File for it + var chunkSum = fileObj.chunkSum || 1; + + // Add fileObj to tracker collection + tracker.upsert(selector, {$setOnInsert: {keys: {}}}); + + // Determine how we're using the writeStream + var isOnePart = false, isMultiPart = false, isStoreSync = false, chunkNum = 0; + if (options === +options) { + isMultiPart = true; + chunkNum = options; + } else if (options === ''+options) { + isStoreSync = true; + } else { + isOnePart = true; + } + + // XXX: it should be possible for a store to sync by storing data into the + // tempstore - this could be done nicely by setting the store name as string + // in the chunk variable? + // This store name could be passed on the the fileworker via the uploaded + // event + // So the uploaded event can return: + // undefined - if data is stored into and should sync out to all storage adapters + // number - if a chunk has been uploaded + // string - if a storage adapter wants to sync its data to the other SA's + + // Find a nice location for the chunk data + var fileKey = _fileReference(fileObj, chunkNum); + + // Create the stream as Meteor safe stream + var writeStream = FS.TempStore.Storage.adapter.createWriteStream(fileKey); + + // When the stream closes we update the chunkCount + writeStream.safeOn('stored', function(result) { + // Save key in tracker document + var setObj = {}; + setObj['keys.' + chunkNum] = result.fileKey; + tracker.update(selector, {$set: setObj}); + + var temp = tracker.findOne(selector); + + if (!temp) { + FS.debug && console.log('NOT FOUND FROM TEMPSTORE => EXIT (REMOVED)'); + return; + } + + // Get updated chunkCount + var chunkCount = FS.Utility.size(temp.keys); + + // Progress + self.emit('progress', fileObj, chunkNum, chunkCount, chunkSum, result); + + var modifier = { $set: {} }; + if (!fileObj.instance_id) { + modifier.$set.instance_id = process.env.COLLECTIONFS_ENV_NAME_UNIQUE_ID ? process.env[process.env.COLLECTIONFS_ENV_NAME_UNIQUE_ID] : process.env.METEOR_PARENT_PID; + } + + // If upload is completed + if (chunkCount === chunkSum) { + // We no longer need the chunk info + modifier.$unset = {chunkCount: 1, chunkSum: 1, chunkSize: 1}; + + // Check if the file has been uploaded before + if (typeof fileObj.uploadedAt === 'undefined') { + // We set the uploadedAt date + modifier.$set.uploadedAt = new Date(); + } else { + // We have been uploaded so an event were file data is updated is + // called synchronizing - so this must be a synchronizedAt? + modifier.$set.synchronizedAt = new Date(); + } + + // Update the fileObject + fileObj.update(modifier); + + // Fire ending events + var eventName = isStoreSync ? 'synchronized' : 'stored'; + self.emit(eventName, fileObj, result); + + // XXX is emitting "ready" necessary? + self.emit('ready', fileObj, chunkCount, result); + } else { + // Update the chunkCount on the fileObject + modifier.$set.chunkCount = chunkCount; + fileObj.update(modifier); + } + }); + + // Emit errors + writeStream.on('error', function (error) { + FS.debug && console.log('TempStore writeStream error:', error); + self.emit('error', error, fileObj); + }); + + return writeStream; +}; + +/** + * @method FS.TempStore.createReadStream + * @public + * @param {FS.File} fileObj The file to read + * @return {Stream} Returns readable stream + * + */ +FS.TempStore.createReadStream = function(fileObj) { + // Ensure that we have a storage adapter mounted; if not, throw an error. + mountStorage(); + + // If fileObj is not mounted or can't be, throw an error + mountFile(fileObj, 'FS.TempStore.createReadStream'); + + FS.debug && console.log('FS.TempStore creating read stream for ' + fileObj._id); + + // Determine how many total chunks there are from the tracker collection + var chunkInfo = tracker.findOne({fileId: fileObj._id, collectionName: fileObj.collectionName}) || {}; + var totalChunks = FS.Utility.size(chunkInfo.keys); + + function getNextStreamFunc(chunk) { + return Meteor.bindEnvironment(function(next) { + var fileKey = _fileReference(fileObj, chunk); + var chunkReadStream = FS.TempStore.Storage.adapter.createReadStream(fileKey); + next(chunkReadStream); + }, function (error) { + throw error; + }); + } + + // Make a combined stream + var combinedStream = CombinedStream.create(); + + // Add each chunk stream to the combined stream when the previous chunk stream ends + var currentChunk = 0; + for (var chunk = 0; chunk < totalChunks; chunk++) { + combinedStream.append(getNextStreamFunc(chunk)); + } + + // Return the combined stream + return combinedStream; +}; diff --git a/packages/wekan-cfs-temp-store/tests/server-tests.js b/packages/wekan-cfs-temp-store/tests/server-tests.js new file mode 100644 index 000000000..242aa89ca --- /dev/null +++ b/packages/wekan-cfs-temp-store/tests/server-tests.js @@ -0,0 +1,39 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-tempstore - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +/* + * This is a server-only package so only server tests are needed. + * Need to test each API method: + * FS.TempStore.saveChunk + * FS.TempStore.getDataForFile + * FS.TempStore.getDataForFileSync + * FS.TempStore.deleteChunks + * FS.TempStore.ensureForFile + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-upload-http/.travis.yml b/packages/wekan-cfs-upload-http/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-upload-http/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-upload-http/LICENSE.md b/packages/wekan-cfs-upload-http/LICENSE.md new file mode 100644 index 000000000..b8d7fab60 --- /dev/null +++ b/packages/wekan-cfs-upload-http/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-upload-http/README.md b/packages/wekan-cfs-upload-http/README.md new file mode 100644 index 000000000..855f50585 --- /dev/null +++ b/packages/wekan-cfs-upload-http/README.md @@ -0,0 +1,8 @@ +wekan-cfs-upload-http +========================= + +This is a Meteor package that provides HTTP uploads for +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. \ No newline at end of file diff --git a/packages/wekan-cfs-upload-http/api.md b/packages/wekan-cfs-upload-http/api.md new file mode 100644 index 000000000..674502a6f --- /dev/null +++ b/packages/wekan-cfs-upload-http/api.md @@ -0,0 +1,24 @@ +## wekan-cfs-upload-http Public API ## + +CollectionFS, HTTP File Upload + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +2MB default upload chunk size +Can be overridden by user with FS.config.uploadChunkSize or per FS.Collection in collection options +- + +### *fsFile*.resume(ref)  Client ### + +*This method __resume__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __ref__ *{[File](#File)|[Blob](#Blob)|Buffer}* + + +> This function is not yet implemented for server + +> ```FS.File.prototype.resume = function(ref) { ...``` [upload-http-client.js:257](upload-http-client.js#L257) + + diff --git a/packages/wekan-cfs-upload-http/internal.api.md b/packages/wekan-cfs-upload-http/internal.api.md new file mode 100644 index 000000000..dd5a57f66 --- /dev/null +++ b/packages/wekan-cfs-upload-http/internal.api.md @@ -0,0 +1,160 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["upload-http-client.js"](upload-http-client.js) Where: {client}__ + +*** + +2MB default upload chunk size +Can be overridden by user with FS.config.uploadChunkSize or per FS.Collection in collection options +- + +### _taskHandler(task, next)  Client ### + +*This method is private* + +__Arguments__ + +* __task__ *{Object}* +* __next__ *{Function}* + +__Returns__ *{undefined}* + + +> ```var _taskHandler = function(task, next) { ...``` [upload-http-client.js:15](upload-http-client.js#L15) + + +- + +### _errorHandler(data, addTask)  Client ### + +*This method is private* + +__Arguments__ + +* __data__ *{Object}* +* __addTask__ *{Function}* + +__Returns__ *{undefined}* + + +> ```var _errorHandler = function(data, addTask, failures) { ...``` [upload-http-client.js:49](upload-http-client.js#L49) + + +- + +### new UploadTransferQueue([options])  Client ### + + +__Arguments__ + +* __options__ *{Object}* (Optional) + +> ```UploadTransferQueue = function(options) { ...``` [upload-http-client.js:60](upload-http-client.js#L60) + + +- + +### *uploadtransferqueue*.isUploadingFile(fileObj)  Client ### + +*This method __isUploadingFile__ is defined in `UploadTransferQueue`* + +__Arguments__ + +* __fileObj__ *{[FS.File](#FS.File)}* + + File to check if uploading + + +__Returns__ *{Boolean}* +True if the file is uploading + +__TODO__ +``` +* Maybe have a similar function for accessing the file upload queue? +``` + + + +> ```self.isUploadingFile = function(fileObj) { ...``` [upload-http-client.js:90](upload-http-client.js#L90) + + +- + +### *uploadtransferqueue*.resumeUploadingFile(File)  Client ### + +*This method __resumeUploadingFile__ is defined in `UploadTransferQueue`* + +__Arguments__ + +* __File__ *{[FS.File](#FS.File)}* + + to resume uploading + + +__TODO__ +``` +* Not sure if this is the best way to handle resumes +``` + +> ```self.resumeUploadingFile = function(fileObj) { ...``` [upload-http-client.js:99](upload-http-client.js#L99) + + +- + +### *uploadtransferqueue*.uploadFile(File)  Client ### + +*This method __uploadFile__ is defined in `UploadTransferQueue`* + +__Arguments__ + +* __File__ *{[FS.File](#FS.File)}* + + to upload + + +__TODO__ +``` +* Check that a file can only be added once - maybe a visual helper on the FS.File? +* Have an initial request to the server getting uploaded chunks for resume +``` + +> ```self.uploadFile = function(fileObj) { ...``` [upload-http-client.js:120](upload-http-client.js#L120) + + +- + +### *fsHttp*.uploadQueue UploadTransferQueue  Client ### + +*This property __uploadQueue__ is defined in `FS.HTTP`* + + +There is a single uploads transfer queue per client (not per CFS) + +> ```FS.HTTP.uploadQueue = new UploadTransferQueue();``` [upload-http-client.js:243](upload-http-client.js#L243) + + +- + +### *fsFile*.resume(ref)  Client ### + +*This method __resume__ is defined in `prototype` of `FS.File`* + +__Arguments__ + +* __ref__ *{[File](#File)|[Blob](#Blob)|Buffer}* + +__TODO__ +``` +* WIP, Not yet implemented for server +``` + + +> This function is not yet implemented for server + +> ```FS.File.prototype.resume = function(ref) { ...``` [upload-http-client.js:257](upload-http-client.js#L257) + + diff --git a/packages/wekan-cfs-upload-http/package.js b/packages/wekan-cfs-upload-http/package.js new file mode 100644 index 000000000..ef76ba94c --- /dev/null +++ b/packages/wekan-cfs-upload-http/package.js @@ -0,0 +1,37 @@ +Package.describe({ + name: 'wekan-cfs-upload-http', + version: '0.0.21', + summary: 'CollectionFS, HTTP File Upload', +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + + api.use([ + 'wekan-cfs-base-package@0.0.30', + 'wekan-cfs-tempstore@0.1.4', + 'wekan-cfs-file@0.1.16', + 'wekan-cfs-access-point@0.1.49', + 'wekan-cfs-power-queue@0.9.11', + 'wekan-cfs-reactive-list@0.0.9' + ]); + + api.addFiles([ + 'upload-http-common.js', + 'upload-http-client.js' + ], 'client'); + + api.addFiles([ + 'upload-http-common.js' + ], 'server'); +}); + +// Package.onTest(function (api) { +// api.use('collectionfs'); +// api.use('test-helpers', 'server'); +// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', +// 'random', 'deps']); + +// api.addFiles('tests/server-tests.js', 'server'); +// api.addFiles('tests/client-tests.js', 'client'); +// }); diff --git a/packages/wekan-cfs-upload-http/tests/client-tests.js b/packages/wekan-cfs-upload-http/tests/client-tests.js new file mode 100644 index 000000000..53ccc6239 --- /dev/null +++ b/packages/wekan-cfs-upload-http/tests/client-tests.js @@ -0,0 +1,27 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-upload-http - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-upload-http/tests/server-tests.js b/packages/wekan-cfs-upload-http/tests/server-tests.js new file mode 100644 index 000000000..7fb4a7f5d --- /dev/null +++ b/packages/wekan-cfs-upload-http/tests/server-tests.js @@ -0,0 +1,27 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-upload-http - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); +}); + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-upload-http/upload-http-client.js b/packages/wekan-cfs-upload-http/upload-http-client.js new file mode 100644 index 000000000..6ac2ff645 --- /dev/null +++ b/packages/wekan-cfs-upload-http/upload-http-client.js @@ -0,0 +1,260 @@ +/* + * HTTP Upload Transfer Queue + */ + +// 2MB default upload chunk size +// Can be overridden by user with FS.config.uploadChunkSize or per FS.Collection in collection options +var defaultChunkSize = 2 * 1024 * 1024; + +/** + * @private + * @param {Object} task + * @param {Function} next + * @return {undefined} + */ +var _taskHandler = function(task, next) { + FS.debug && console.log("uploading chunk " + task.chunk + ", bytes " + task.start + " to " + Math.min(task.end, task.fileObj.size()) + " of " + task.fileObj.size()); + task.fileObj.data.getBinary(task.start, task.end, function gotBinaryCallback(err, data) { + if (err) { + next(new Meteor.Error(err.error, err.message)); + } else { + + FS.debug && console.log('PUT to URL', task.url, task.urlParams); + + HTTP.call("PUT", task.url, { + params: FS.Utility.extend({chunk: task.chunk}, task.urlParams), + content: data, + headers: { + 'Content-Type': task.fileObj.type() + } + }, function(error, result) { + task = null; + if (error) { + next(new Meteor.Error(error.error, error.message)); + } else { + next(); + } + }); + + } + }); +}; + +/** + * @private + * @param {Object} data + * @param {Function} addTask + * @return {undefined} + */ +var _errorHandler = function(data, addTask, failures) { + // If file upload fails + // TODO We should retry a few times and then emit error? + // data.fileObj.emit("error", error); +}; + +/** @method UploadTransferQueue + * @namespace UploadTransferQueue + * @constructor + * @param {Object} [options] + */ +UploadTransferQueue = function(options) { + // Rig options + options = options || {}; + + // Init the power queue + var self = new PowerQueue({ + name: 'HTTPUploadTransferQueue', + // spinalQueue: ReactiveList, + maxProcessing: 1, + maxFailures: 5, + jumpOnFailure: true, + autostart: true, + isPaused: false, + filo: false, + debug: FS.debug + }); + + // Keep track of uploaded files via this queue + self.files = {}; + + // cancel maps onto queue reset + self.cancel = self.reset; + + /** + * @method UploadTransferQueue.isUploadingFile + * @param {FS.File} fileObj File to check if uploading + * @returns {Boolean} True if the file is uploading + * + * @todo Maybe have a similar function for accessing the file upload queue? + */ + self.isUploadingFile = function(fileObj) { + // Check if file is already in queue + return !!(fileObj && fileObj._id && fileObj.collectionName && (self.files[fileObj.collectionName] || {})[fileObj._id]); + }; + + /** @method UploadTransferQueue.resumeUploadingFile + * @param {FS.File} File to resume uploading + * @todo Not sure if this is the best way to handle resumes + */ + self.resumeUploadingFile = function(fileObj) { + // Make sure we are handed a FS.File + if (!(fileObj instanceof FS.File)) { + throw new Error('Transfer queue expects a FS.File'); + } + + if (fileObj.isMounted()) { + // This might still be true, preventing upload, if + // there was a server restart without client restart. + self.files[fileObj.collectionName] = self.files[fileObj.collectionName] || {}; + self.files[fileObj.collectionName][fileObj._id] = false; + // Kick off normal upload + self.uploadFile(fileObj); + } + }; + + /** @method UploadTransferQueue.uploadFile + * @param {FS.File} File to upload + * @todo Check that a file can only be added once - maybe a visual helper on the FS.File? + * @todo Have an initial request to the server getting uploaded chunks for resume + */ + self.uploadFile = function(fileObj) { + FS.debug && console.log("HTTP uploadFile"); + + // Make sure we are handed a FS.File + if (!(fileObj instanceof FS.File)) { + throw new Error('Transfer queue expects a FS.File'); + } + + // Make sure that we have size as number + if (typeof fileObj.size() !== 'number') { + throw new Error('TransferQueue upload failed: fileObj size not set'); + } + + // We don't add the file if it's already in transfer or if already uploaded + if (self.isUploadingFile(fileObj) || fileObj.isUploaded()) { + return; + } + + // Make sure the file object is mounted on a collection + if (fileObj.isMounted()) { + + var collectionName = fileObj.collectionName; + var id = fileObj._id; + + // Set the chunkSize to match the collection options, or global config, or default + fileObj.chunkSize = fileObj.collection.options.chunkSize || FS.config.uploadChunkSize || defaultChunkSize; + // Set counter for uploaded chunks + fileObj.chunkCount = 0; + // Calc the number of chunks + fileObj.chunkSum = Math.ceil(fileObj.size() / fileObj.chunkSize); + + if (fileObj.chunkSum === 0) + return; + + // Update the filerecord + // TODO eventually we should be able to do this without storing any chunk info in the filerecord + fileObj.update({$set: {chunkSize: fileObj.chunkSize, chunkCount: fileObj.chunkCount, chunkSum: fileObj.chunkSum}}); + + // Create a sub queue + var chunkQueue = new PowerQueue({ + onEnded: function oneChunkQueueEnded() { + // Remove from list of files being uploaded + self.files[collectionName][id] = false; + // XXX It might be possible for this to be called even though there were errors uploading? + fileObj.emit("uploaded"); + }, + spinalQueue: ReactiveList, + maxProcessing: 1, + maxFailures: 5, + jumpOnFailure: true, + autostart: false, + isPaused: false, + filo: false + }); + + // Rig the custom task handler + chunkQueue.taskHandler = _taskHandler; + + // Rig the error handler + chunkQueue.errorHandler = _errorHandler; + + // Set flag that this file is being transfered + self.files[collectionName] = self.files[collectionName] || {}; + self.files[collectionName][id] = true; + + // Construct URL + var url = FS.HTTP.uploadUrl + '/' + collectionName; + if (id) { + url += '/' + id; + } + + // TODO: Could we somehow figure out if the collection requires login? + var authToken = ''; + if (typeof Accounts !== "undefined") { + var authObject = { + authToken: Accounts._storedLoginToken() || '', + }; + + // Set the authToken + var authString = JSON.stringify(authObject); + authToken = FS.Utility.btoa(authString); + } + + // Construct query string + var urlParams = { + filename: fileObj.name() + }; + if (authToken !== '') { + urlParams.token = authToken; + } + + // Add chunk upload tasks + for (var chunk = 0, start; chunk < fileObj.chunkSum; chunk++) { + start = chunk * fileObj.chunkSize; + // Create and add the task + // XXX should we somehow make sure we haven't uploaded this chunk already, in + // case we are resuming? + chunkQueue.add({ + chunk: chunk, + name: fileObj.name(), + url: url, + urlParams: urlParams, + fileObj: fileObj, + start: start, + end: (chunk + 1) * fileObj.chunkSize + }); + } + + // Add the queue to the main upload queue + self.add(chunkQueue); + } + + }; + + return self; +}; + +/** + * @namespace FS + * @type UploadTransferQueue + * + * There is a single uploads transfer queue per client (not per CFS) + */ +FS.HTTP.uploadQueue = new UploadTransferQueue(); + +/* + * FS.File extensions + */ + +/** + * @method FS.File.prototype.resume + * @public + * @param {File|Blob|Buffer} ref + * @todo WIP, Not yet implemented for server + * + * > This function is not yet implemented for server + */ +FS.File.prototype.resume = function(ref) { + var self = this; + FS.uploadQueue.resumeUploadingFile(self); +}; diff --git a/packages/wekan-cfs-upload-http/upload-http-common.js b/packages/wekan-cfs-upload-http/upload-http-common.js new file mode 100644 index 000000000..4c630a385 --- /dev/null +++ b/packages/wekan-cfs-upload-http/upload-http-common.js @@ -0,0 +1 @@ +FS.HTTP = FS.HTTP || {}; diff --git a/packages/wekan-cfs-worker/.travis.yml b/packages/wekan-cfs-worker/.travis.yml new file mode 100644 index 000000000..6a4640033 --- /dev/null +++ b/packages/wekan-cfs-worker/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/s0Zu-w | /bin/sh" \ No newline at end of file diff --git a/packages/wekan-cfs-worker/CHANGELOG.md b/packages/wekan-cfs-worker/CHANGELOG.md new file mode 100644 index 000000000..84d63d329 --- /dev/null +++ b/packages/wekan-cfs-worker/CHANGELOG.md @@ -0,0 +1,123 @@ +# Changelog + +## vCurrent +## [v0.1.2] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.1.2) +#### 17/12/14 by Morten Henriksen +## [v0.1.1] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.1.1) +#### 17/12/14 by Morten Henriksen +- Bump to version 0.1.1 + +- mbr update, remove versions.json + +## [v0.1.0] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.1.0) +#### 17/12/14 by Morten Henriksen +- mbr update versions and fix warnings + +- *Merged pull-request:* "Remove the unused function makeSafeCallback()." [#4](https://github.com/zcfs/Meteor-cfs-worker/issues/4) ([DouglasUrner](https://github.com/DouglasUrner)) + +- Remove the unused function makeSafeCallback(). + +- *Merged pull-request:* "Minor formatting edit." [#2](https://github.com/zcfs/Meteor-cfs-worker/issues/2) ([DouglasUrner](https://github.com/DouglasUrner)) + +- Minor formatting edit. + +- 0.9.1 support + +Patches by GitHub user [@DouglasUrner](https://github.com/DouglasUrner). + +## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.20) +#### 28/08/14 by Morten Henriksen +- Meteor Package System Update + +## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.19) +#### 27/08/14 by Eric Dobbertin +## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.18) +#### 27/08/14 by Eric Dobbertin +- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-worker + +- change package name to lowercase + +## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.17) +#### 09/08/14 by Eric Dobbertin +- *Merged pull-request:* "Fixed bug preventing temp chunks deletion" [#1](https://github.com/zcfs/Meteor-cfs-worker/issues/1) ([GuillaumeZuff](https://github.com/GuillaumeZuff)) + +- Fixed bug preventing temp chunks deletion + +Patches by GitHub user [@GuillaumeZuff](https://github.com/GuillaumeZuff). + +## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.16) +#### 06/04/14 by Eric Dobbertin +- use uploadedAt so that we can remove chunk info when it's no longer needed + +## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.15) +#### 05/04/14 by Morten Henriksen +## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.14) +#### 31/03/14 by Eric Dobbertin +- use latest releases + +## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.13) +#### 29/03/14 by Morten Henriksen +- remove underscore deps + +## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.12) +#### 29/03/14 by Morten Henriksen +- Refactoring and clean ups + +## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.11) +#### 23/03/14 by Morten Henriksen +- Rollback to specific git dependency + +- use collectionFS travis version force update + +## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.10) +#### 22/03/14 by Morten Henriksen +- try to fix travis test by using general package references + +## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.9) +#### 22/03/14 by Morten Henriksen +- out factor fileobj update when file is stored + +- clean up and use the correct end event for streams + +- reference released collectionFS pkg + +## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.8) +#### 21/03/14 by Morten Henriksen +## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.7) +#### 18/03/14 by Eric Dobbertin +- Remove from temp store when removed from collection + +## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.6) +#### 18/03/14 by Morten Henriksen +- fix refactor name for removeFile + +- add back code for running beforeSave function, with stream support (just getting it working for now, can be switched to transform streams later) + +- changed method name + +- minor changes using chunks in file record + +- Add streaming WIP + +## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.5) +#### 07/03/14 by Eric Dobbertin +- should be installing devel + +- small change because tempstore no longer tracks chunks in the file object + +## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.4) +#### 03/03/14 by Eric Dobbertin +- moved beforeSave out of SA and into here + +- move saveCopy here, out of fs.collection + +- just package and doc tweaks + +## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.3) +#### 15/02/14 by Morten Henriksen +## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.2) +#### 13/02/14 by Morten Henriksen +## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-worker/tree/v0.0.1) +#### 13/02/14 by Morten Henriksen +- init commit + diff --git a/packages/wekan-cfs-worker/LICENSE.md b/packages/wekan-cfs-worker/LICENSE.md new file mode 100644 index 000000000..1a3820821 --- /dev/null +++ b/packages/wekan-cfs-worker/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/wekan-cfs-worker/README.md b/packages/wekan-cfs-worker/README.md new file mode 100644 index 000000000..f45ef8df6 --- /dev/null +++ b/packages/wekan-cfs-worker/README.md @@ -0,0 +1,8 @@ +wekan-cfs-worker +========================= + +This is a Meteor package used by +[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS). + +You don't need to manually add this package to your app. It is added when you +add the `wekan-cfs-standard-packages` package. \ No newline at end of file diff --git a/packages/wekan-cfs-worker/api.md b/packages/wekan-cfs-worker/api.md new file mode 100644 index 000000000..cab1605d5 --- /dev/null +++ b/packages/wekan-cfs-worker/api.md @@ -0,0 +1,38 @@ +## cfs-worker Public API ## + +CollectionFS, file worker - handles file copies/versions + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +TODO: Use power queue to handle throttling etc. +Use observe to monitor changes and have it create tasks for the power queue +to perform. +- + +### *fs*.FileWorker Object  Server ### + +*This property __FileWorker__ is defined in `FS`* + + +> ```FS.FileWorker = { ...``` [fileWorker.js:9](fileWorker.js#L9) + + +- + +### *fsFileworker*.observe(fsCollection)  Server ### + +*This method __observe__ is defined in `FS.FileWorker`* + +__Arguments__ + +* __fsCollection__ *{[FS.Collection](#FS.Collection)}* + +__Returns__ *{undefined}* + + +Sets up observes on the fsCollection to store file copies and delete +temp files at the appropriate times. + +> ```FS.FileWorker.observe = function(fsCollection) { ...``` [fileWorker.js:20](fileWorker.js#L20) + + diff --git a/packages/wekan-cfs-worker/fileWorker.js b/packages/wekan-cfs-worker/fileWorker.js new file mode 100644 index 000000000..8d40da3e8 --- /dev/null +++ b/packages/wekan-cfs-worker/fileWorker.js @@ -0,0 +1,185 @@ +//// TODO: Use power queue to handle throttling etc. +//// Use observe to monitor changes and have it create tasks for the power queue +//// to perform. + +/** + * @public + * @type Object + */ +FS.FileWorker = {}; + +/** + * @method FS.FileWorker.observe + * @public + * @param {FS.Collection} fsCollection + * @returns {undefined} + * + * Sets up observes on the fsCollection to store file copies and delete + * temp files at the appropriate times. + */ +FS.FileWorker.observe = function(fsCollection) { + + // Initiate observe for finding newly uploaded/added files that need to be stored + // per store. + FS.Utility.each(fsCollection.options.stores, function(store) { + var storeName = store.name; + fsCollection.files.find(getReadyQuery(storeName), { + fields: { + copies: 0 + } + }).observe({ + added: function(fsFile) { + // added will catch fresh files + FS.debug && console.log("FileWorker ADDED - calling saveCopy", storeName, "for", fsFile._id); + saveCopy(fsFile, storeName); + }, + changed: function(fsFile) { + // changed will catch failures and retry them + FS.debug && console.log("FileWorker CHANGED - calling saveCopy", storeName, "for", fsFile._id); + saveCopy(fsFile, storeName); + } + }); + }); + + // Initiate observe for finding files that have been stored so we can delete + // any temp files + fsCollection.files.find(getDoneQuery(fsCollection.options.stores)).observe({ + added: function(fsFile) { + FS.debug && console.log("FileWorker ADDED - calling deleteChunks for", fsFile._id); + try { + FS.TempStore.removeFile(fsFile); + } catch(err) { + console.error(err); + } + } + }); + + // Initiate observe for catching files that have been removed and + // removing the data from all stores as well + fsCollection.files.find().observe({ + removed: function(fsFile) { + FS.debug && console.log('FileWorker REMOVED - removing all stored data for', fsFile._id); + //remove from temp store + FS.TempStore.removeFile(fsFile); + //delete from all stores + FS.Utility.each(fsCollection.options.stores, function(storage) { + storage.adapter.remove(fsFile); + }); + } + }); +}; + +/** + * @method getReadyQuery + * @private + * @param {string} storeName - The name of the store to observe + * + * Returns a selector that will be used to identify files that + * have been uploaded but have not yet been stored to the + * specified store. + * + * { + * uploadedAt: {$exists: true}, + * 'copies.storeName`: null, + * 'failures.copies.storeName.doneTrying': {$ne: true} + * } + */ +function getReadyQuery(storeName) { + var selector = {uploadedAt: {$exists: true}}; + selector['copies.' + storeName] = null; + selector['failures.copies.' + storeName + '.doneTrying'] = {$ne: true}; + return selector; +} + +/** + * @method getDoneQuery + * @private + * @param {Array} stores - The stores array from the FS.Collection options + * + * Returns a selector that will be used to identify files where all + * stores have successfully save or have failed the + * max number of times but still have chunks. The resulting selector + * should be something like this: + * + * { + * $and: [ + * {chunks: {$exists: true}}, + * { + * $or: [ + * { + * $and: [ + * { + * 'copies.storeName': {$ne: null} + * }, + * { + * 'copies.storeName': {$ne: false} + * } + * ] + * }, + * { + * 'failures.copies.storeName.doneTrying': true + * } + * ] + * }, + * REPEATED FOR EACH STORE + * ] + * } + * + */ +function getDoneQuery(stores) { + var selector = { + $and: [{chunks: {$exists: true}}] + }; + + // Add conditions for all defined stores + FS.Utility.each(stores, function(store) { + var storeName = store.name; + var copyCond = {$or: [{$and: []}]}; + var tempCond = {}; + tempCond["copies." + storeName] = {$ne: null}; + copyCond.$or[0].$and.push(tempCond); + tempCond = {}; + tempCond["copies." + storeName] = {$ne: false}; + copyCond.$or[0].$and.push(tempCond); + tempCond = {}; + tempCond['failures.copies.' + storeName + '.doneTrying'] = true; + copyCond.$or.push(tempCond); + selector.$and.push(copyCond); + }) + + return selector; +} + +/** + * @method saveCopy + * @private + * @param {FS.File} fsFile + * @param {string} storeName + * @param {Object} options + * @param {Boolean} [options.overwrite=false] - Force save to the specified store? + * @returns {undefined} + * + * Saves to the specified store. If the + * `overwrite` option is `true`, will save to the store even if we already + * have, potentially overwriting any previously saved data. Synchronous. + */ +function saveCopy(fsFile, storeName, options) { + options = options || {}; + + var storage = FS.StorageAdapter(storeName); + if (!storage) { + throw new Error('No store named "' + storeName + '" exists'); + } + + FS.debug && console.log('saving to store ' + storeName); + + try { + var writeStream = storage.adapter.createWriteStream(fsFile); + var readStream = FS.TempStore.createReadStream(fsFile); + + // Pipe the temp data into the storage adapter + readStream.pipe(writeStream); + } catch(err) { + console.error(err); + } +} diff --git a/packages/wekan-cfs-worker/internal.api.md b/packages/wekan-cfs-worker/internal.api.md new file mode 100644 index 000000000..92cce610b --- /dev/null +++ b/packages/wekan-cfs-worker/internal.api.md @@ -0,0 +1,143 @@ +## Public and Private API ## + +_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._ + +*** + +__File: ["fileWorker.js"](fileWorker.js) Where: {server}__ + +*** + +TODO: Use power queue to handle throttling etc. +Use observe to monitor changes and have it create tasks for the power queue +to perform. + +- + +### *fs*.FileWorker Object  Server ### + +*This property __FileWorker__ is defined in `FS`* + + +> ```FS.FileWorker = { ...``` [fileWorker.js:9](fileWorker.js#L9) + + +- + +### *fsFileworker*.observe(fsCollection)  Server ### + +*This method __observe__ is defined in `FS.FileWorker`* + +__Arguments__ + +* __fsCollection__ *{[FS.Collection](#FS.Collection)}* + +__Returns__ *{undefined}* + + +Sets up observes on the fsCollection to store file copies and delete +temp files at the appropriate times. + +> ```FS.FileWorker.observe = function(fsCollection) { ...``` [fileWorker.js:20](fileWorker.js#L20) + + +- + +### getReadyQuery(storeName)  undefined ### + +*This method is private* + +__Arguments__ + +* __storeName__ *{string}* + + The name of the store to observe + + + +Returns a selector that will be used to identify files that +have been uploaded but have not yet been stored to the +specified store. + +{ +$where: "this.chunkSum === this.chunkCount", +'copies.storeName`: null, +'failures.copies.storeName.doneTrying': {$ne: true} +} + +> ```function getReadyQuery(storeName) { ...``` [fileWorker.js:83](fileWorker.js#L83) + + +- + +### getDoneQuery(stores)  undefined ### + +*This method is private* + +__Arguments__ + +* __stores__ *{Object}* + + The stores object from the FS.Collection options + + + +Returns a selector that will be used to identify files where all +stores have successfully save or have failed the +max number of times but still have chunks. The resulting selector +should be something like this: + +{ +$and: [ +{chunks: {$exists: true}}, +{ +$or: [ +{ +$and: [ +{ +'copies.storeName': {$ne: null} +}, +{ +'copies.storeName': {$ne: false} +} +] +}, +{ +'failures.copies.storeName.doneTrying': true +} +] +}, +REPEATED FOR EACH STORE +] +} + + +> ```function getDoneQuery(stores) { ...``` [fileWorker.js:129](fileWorker.js#L129) + + +- + +### saveCopy(fsFile, storeName, options)  Server ### + +*This method is private* + +__Arguments__ + +* __fsFile__ *{[FS.File](#FS.File)}* +* __storeName__ *{string}* +* __options__ *{Object}* + * __overwrite__ *{Boolean}* (Optional, Default = false) + + Force save to the specified store? + + +__Returns__ *{undefined}* + + +Saves to the specified store. If the +`overwrite` option is `true`, will save to the store even if we already +have, potentially overwriting any previously saved data. Synchronous. + +> ```var makeSafeCallback = function (callback) { ...``` [fileWorker.js:168](fileWorker.js#L168) + + diff --git a/packages/wekan-cfs-worker/package.js b/packages/wekan-cfs-worker/package.js new file mode 100644 index 000000000..e8bc72058 --- /dev/null +++ b/packages/wekan-cfs-worker/package.js @@ -0,0 +1,34 @@ +Package.describe({ + git: 'https://github.com/zcfs/Meteor-cfs-worker.git', + name: 'wekan-cfs-worker', + version: '0.1.5', + summary: 'CollectionFS, file worker - handles file copies/versions' +}); + +Package.onUse(function(api) { + api.use([ + 'wekan-cfs-base-package@0.0.30', + 'wekan-cfs-tempstore@0.1.6', + 'wekan-cfs-storage-adapter@0.2.1' + ]); + + api.use([ + 'livedata@1.0.0', + 'mongo-livedata@1.0.0', + 'wekan-cfs-power-queue@0.9.11' + ]); + + api.addFiles([ + 'fileWorker.js' + ], 'server'); +}); + +// Package.on_test(function (api) { +// api.use('wekan-cfs-standard-packages@0.0.0'); + +// api.use('test-helpers', 'server'); +// api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict', 'random']); + +// api.addFiles('tests/client-tests.js', 'client'); +// api.addFiles('tests/server-tests.js', 'server'); +// }); diff --git a/packages/wekan-cfs-worker/tests/client-tests.js b/packages/wekan-cfs-worker/tests/client-tests.js new file mode 100644 index 000000000..49a100846 --- /dev/null +++ b/packages/wekan-cfs-worker/tests/client-tests.js @@ -0,0 +1,44 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-worker - client - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Client Tests + * + * construct FS.File with no arguments + * construct FS.File passing in File + * construct FS.File passing in Blob + * load blob into FS.File and then call FS.File.toDataUrl + * call FS.File.setDataFromBinary, then FS.File.getBlob(); make sure correct data is returned + * load blob into FS.File and then call FS.File.getBinary() with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * load blob into FS.File and make sure FS.File.saveLocal initiates a download (possibly can't do automatically) + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-cfs-worker/tests/server-tests.js b/packages/wekan-cfs-worker/tests/server-tests.js new file mode 100644 index 000000000..713ebc07c --- /dev/null +++ b/packages/wekan-cfs-worker/tests/server-tests.js @@ -0,0 +1,49 @@ +function equals(a, b) { + return !!(EJSON.stringify(a) === EJSON.stringify(b)); +} + +Tinytest.add('cfs-worker - server - test environment', function(test) { + test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection'); + test.isTrue(typeof CFSErrorType !== 'undefined', 'test environment not initialized CFSErrorType'); +}); + +/* + * FS.File Server Tests + * + * construct FS.File with no arguments + * load data with FS.File.setDataFromBuffer + * load data with FS.File.setDataFromBinary + * load data and then call FS.File.toDataUrl with and without callback + * load buffer into FS.File and then call FS.File.getBinary with and without start/end; make sure correct data is returned + * construct FS.File, set FS.File.collectionName to a CFS name, and then test FS.File.update/remove/get/put/del/url + * (call these with and without callback to test sync vs. async) + * set FS.File.name to a filename and test that FS.File.getExtension() returns the extension + * + * + * FS.Collection Server Tests + * + * Make sure options.filter is respected + * + * + */ + + +//Test API: +//test.isFalse(v, msg) +//test.isTrue(v, msg) +//test.equalactual, expected, message, not +//test.length(obj, len) +//test.include(s, v) +//test.isNaN(v, msg) +//test.isUndefined(v, msg) +//test.isNotNull +//test.isNull +//test.throws(func) +//test.instanceOf(obj, klass) +//test.notEqual(actual, expected, message) +//test.runId() +//test.exception(exception) +//test.expect_fail() +//test.ok(doc) +//test.fail(doc) +//test.equal(a, b, msg) diff --git a/packages/wekan-ldap/server/ldap.js b/packages/wekan-ldap/server/ldap.js index 50f0daa88..8a59ee7c3 100644 --- a/packages/wekan-ldap/server/ldap.js +++ b/packages/wekan-ldap/server/ldap.js @@ -19,7 +19,7 @@ export default class LDAP { idle_timeout : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'), encryption : this.constructor.settings_get('LDAP_ENCRYPTION'), ca_cert : this.constructor.settings_get('LDAP_CA_CERT'), - reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false, + reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') !== undefined ? this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') : true, Authentication : this.constructor.settings_get('LDAP_AUTHENTIFICATION'), Authentication_UserDN : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'), Authentication_Password : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'), @@ -102,7 +102,7 @@ export default class LDAP { if (this.options.ca_cert && this.options.ca_cert !== '') { // Split CA cert into array of strings - const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n'); + const chainLines = this.constructor.settings_get('LDAP_CA_CERT').replace(/\\n/g,'\n').split('\n'); let cert = []; const ca = []; chainLines.forEach((line) => { diff --git a/packages/wekan-ldap/server/loginHandler.js b/packages/wekan-ldap/server/loginHandler.js index 79b3899a0..090ef9da3 100644 --- a/packages/wekan-ldap/server/loginHandler.js +++ b/packages/wekan-ldap/server/loginHandler.js @@ -56,17 +56,17 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) { throw new Error('User not Found'); } - if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) { - if (ldap.isUserInGroup(loginRequest.username, users[0])) { - ldapUser = users[0]; - } else { - throw new Error('User not in a valid group'); - } - } else { - log_info('Wrong password for', loginRequest.username); - } - } + if (ldap.isUserInGroup(loginRequest.username, users[0])) { + ldapUser = users[0]; + } else { + throw new Error('User not in a valid group'); + } + if (ldap.authSync(users[0].dn, loginRequest.ldapPass) !== true) { + ldapUser = null; + log_info('Wrong password for', loginRequest.username) + } + } } catch (error) { log_error(error); diff --git a/packages/wekan-ldap/server/sync.js b/packages/wekan-ldap/server/sync.js index dd3855d35..f1b25aea8 100644 --- a/packages/wekan-ldap/server/sync.js +++ b/packages/wekan-ldap/server/sync.js @@ -247,6 +247,22 @@ export function syncUserData(user, ldapUser) { } } + if (LDAP.settings_get('LDAP_EMAIL_FIELD') !== '') { + const email = getLdapEmail(ldapUser); + log_debug('email=', email); + + if (user && user._id && email !== '') { + log_info('Syncing user email:', email); + Meteor.users.update({ + _id: user._id + }, { + $set: { + 'emails.0.address': email, + } + }); + } + } + } export function addLdapUser(ldapUser, username, password) { diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index c6d7deabc..97f20519b 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -1,4 +1,17 @@ Oidc = {}; +httpCa = false; + +if (process.env.OAUTH2_CA_CERT !== undefined) { + try { + const fs = Npm.require('fs'); + if (fs.existsSync(process.env.OAUTH2_CA_CERT)) { + httpCa = fs.readFileSync(process.env.OAUTH2_CA_CERT); + } + } catch(e) { + console.log('WARNING: failed loading: ' + process.env.OAUTH2_CA_CERT); + console.log(e); + } +} OAuth.registerService('oidc', 2, null, function (query) { @@ -9,8 +22,22 @@ OAuth.registerService('oidc', 2, null, function (query) { var accessToken = token.access_token || token.id_token; var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10)); - var userinfo = getUserInfo(accessToken); + var claimsInAccessToken = (process.env.OAUTH2_ADFS_ENABLED === 'true' || process.env.OAUTH2_ADFS_ENABLED === true) || false; + + var userinfo; + if(claimsInAccessToken) + { + // hack when using custom claims in the accessToken. On premise ADFS + userinfo = getTokenContent(accessToken); + } + else + { + // normal behaviour, getting the claims from UserInfo endpoint. + userinfo = getUserInfo(accessToken); + } + if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack + if (userinfo.metadata) userinfo = userinfo.metadata // Openshift hack if (debug) console.log('XXX: userinfo:', userinfo); var serviceData = {}; @@ -19,7 +46,19 @@ OAuth.registerService('oidc', 2, null, function (query) { serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; serviceData.accessToken = accessToken; serviceData.expiresAt = expiresAt; - serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; + + // If on Oracle OIM email is empty or null, get info from username + if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { + if (userinfo[process.env.OAUTH2_EMAIL_MAP]) { + serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; + } else { + serviceData.email = userinfo[process.env.OAUTH2_USERNAME_MAP]; + } + } + + if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { + serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; + } if (accessToken) { var tokenContent = getTokenContent(accessToken); @@ -47,47 +86,110 @@ if (Meteor.release) { userAgent += "/" + Meteor.release; } -var getToken = function (query) { - var debug = process.env.DEBUG || false; - var config = getConfiguration(); - if(config.tokenEndpoint.includes('https://')){ - var serverTokenEndpoint = config.tokenEndpoint; - }else{ - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; - } - var requestPermissions = config.requestPermissions; - var response; +if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { + var getToken = function (query) { + var debug = process.env.DEBUG || false; + var config = getConfiguration(); + if(config.tokenEndpoint.includes('https://')){ + var serverTokenEndpoint = config.tokenEndpoint; + }else{ + var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + } + var requestPermissions = config.requestPermissions; + var response; - try { - response = HTTP.post( - serverTokenEndpoint, - { - headers: { - Accept: 'application/json', - "User-Agent": userAgent - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - } + try { + var postOptions = { + headers: { + Accept: 'application/json', + "User-Agent": userAgent + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + } + }; + if (httpCa) { + postOptions['npmRequestOptions'] = { ca: httpCa }; } - ); - } catch (err) { - throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), - { response: err.response }); - } - if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); - } else { - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; - } -}; + response = HTTP.post(serverTokenEndpoint, postOptions); + } catch (err) { + throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), + { response: err.response }); + } + if (response.data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); + } else { + if (debug) console.log('XXX: getToken response: ', response.data); + return response.data; + } + }; +} + +if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { + + var getToken = function (query) { + var debug = (process.env.DEBUG === 'true' || process.env.DEBUG === true) || false; + var config = getConfiguration(); + if(config.tokenEndpoint.includes('https://')){ + var serverTokenEndpoint = config.tokenEndpoint; + }else{ + var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + } + var requestPermissions = config.requestPermissions; + var response; + + // OIM needs basic Authentication token in the header - ClientID + SECRET in base64 + var dataToken=null; + var strBasicToken=null; + var strBasicToken64=null; + + dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; + strBasicToken = new Buffer(dataToken); + strBasicToken64 = strBasicToken.toString('base64'); + + // eslint-disable-next-line no-console + if (debug) console.log('Basic Token: ', strBasicToken64); + + try { + var postOptions = { + headers: { + Accept: 'application/json', + "User-Agent": userAgent, + "Authorization": "Basic " + strBasicToken64 + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + } + }; + if (httpCa) { + postOptions['npmRequestOptions'] = { ca: httpCa }; + } + response = HTTP.post(serverTokenEndpoint, postOptions); + } catch (err) { + throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), + { response: err.response }); + } + if (response.data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); + } else { + // eslint-disable-next-line no-console + if (debug) console.log('XXX: getToken response: ', response.data); + return response.data; + } + }; +} var getUserInfo = function (accessToken) { var debug = process.env.DEBUG || false; @@ -101,15 +203,16 @@ var getUserInfo = function (accessToken) { } var response; try { - response = HTTP.get( - serverUserinfoEndpoint, - { + var getOptions = { headers: { "User-Agent": userAgent, "Authorization": "Bearer " + accessToken } - } - ); + }; + if (httpCa) { + getOptions['npmRequestOptions'] = { ca: httpCa }; + } + response = HTTP.get(serverUserinfoEndpoint, getOptions); } catch (err) { throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), {response: err.response}); diff --git a/packages/wekan-request/.gitignore b/packages/wekan-request/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/packages/wekan-request/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/wekan-request/.npmignore b/packages/wekan-request/.npmignore new file mode 100644 index 000000000..80e59ef52 --- /dev/null +++ b/packages/wekan-request/.npmignore @@ -0,0 +1,2 @@ +tests +node_modules diff --git a/packages/wekan-request/.travis.yml b/packages/wekan-request/.travis.yml new file mode 100644 index 000000000..0bce81526 --- /dev/null +++ b/packages/wekan-request/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - 0.8 + - 0.10 + +env: + - OPTIONALS=Y + - OPTIONALS=N + +install: + - if [[ "$OPTIONALS" == "Y" ]]; then npm install; fi + - if [[ "$OPTIONALS" == "N" ]]; then npm install --no-optional; fi diff --git a/packages/wekan-request/CHANGELOG.md b/packages/wekan-request/CHANGELOG.md new file mode 100644 index 000000000..11f571f23 --- /dev/null +++ b/packages/wekan-request/CHANGELOG.md @@ -0,0 +1,954 @@ +## Change Log + +### upcoming (2014/07/09 12:10 +00:00) +- [#946](https://github.com/mikeal/request/pull/946) defaults: merge headers (@aj0strow) +- [#844](https://github.com/mikeal/request/pull/844) Add support for HTTP[S]_PROXY environment variables. Fixes #595. (@jvmccarthy) + +### v2.37.1 (2014/07/07 17:25 +00:00) +- [8711b2f](https://github.com/mikeal/request/commit/8711b2f3489553a7ddae69fa8c9f538182c9d5c8) 2.37.1 (@mikeal) + +### v2.37.0 (2014/07/07 17:25 +00:00) +- [79472b2](https://github.com/mikeal/request/commit/79472b263cde77504a354913a16bdc9fbdc9ed5d) append secureOptions to poolKey (@medovob) +- [#907](https://github.com/mikeal/request/pull/907) append secureOptions to poolKey (@medovob) +- [b223a8a](https://github.com/mikeal/request/commit/b223a8add0cbdd4e699a52da66aeb0f0cb17a0c3) expose tough-cookie's getCookiesSync (@charlespwd) +- [f4dcad0](https://github.com/mikeal/request/commit/f4dcad0fa6e2f2388abae508ad7256a1e1214ab2) test getCookies method (@charlespwd) +- [adcf62b](https://github.com/mikeal/request/commit/adcf62bf45ec19a28198ca8d3f37e7d7babc883a) update readme (@charlespwd) +- [4fdf13b](https://github.com/mikeal/request/commit/4fdf13b57dcd20b9fe03c0956f5df70c82d6e4a3) Merge branch 'charlespwd-master' (@lalitkapoor) +- [83e370d](https://github.com/mikeal/request/commit/83e370d54ca2a5fb162e40e7e705e1e9d702ba0a) Bump version of hawk dep. (@samccone) +- [#927](https://github.com/mikeal/request/pull/927) Bump version of hawk dep. (@samccone) +- [c42dcec](https://github.com/mikeal/request/commit/c42dcec10a307cb2299861f87720d491a89142b4) package.json: use OSI-style license name (@isaacs) +- [8892cb7](https://github.com/mikeal/request/commit/8892cb7bb8945807ff25038e888222d4e902acc8) Swap mime module. (@eiriksm) +- [d92395e](https://github.com/mikeal/request/commit/d92395e638cbfe5c31eb4ff54941b98b09057486) Make package.json so node .8 understands it. (@eiriksm) +- [6ebd748](https://github.com/mikeal/request/commit/6ebd748a02a49976d41ebbc4f8396acf8fda1c14) Add some additional hacks to work in the browser. (@eiriksm) +- [#943](https://github.com/mikeal/request/pull/943) New mime module (@eiriksm) +- [561454d](https://github.com/mikeal/request/commit/561454d18a68b7a03163308f6d29e127afe97426) Add some code comments about why we do the extra checks. (@eiriksm) +- [#944](https://github.com/mikeal/request/pull/944) Make request work with browserify (@eiriksm) +- [6a0add7](https://github.com/mikeal/request/commit/6a0add70b2687cf751b3446a15a513a1fd141738) defaults: merge headers (@aj0strow) +- [407c1ad](https://github.com/mikeal/request/commit/407c1ada61afca4d4ba50155c6d9430754541df1) prefer late return statement (@aj0strow) +- [4ab40ba](https://github.com/mikeal/request/commit/4ab40ba2f9aca8958cab149eb9cfbd9edb5534aa) Added support for manual querystring in form option (@charlespwd) +- [a55627c](https://github.com/mikeal/request/commit/a55627cd9f468cefb2971bb501ebc0c2fc27aa8b) Updated README (@charlespwd) +- [#949](https://github.com/mikeal/request/pull/949) Manually enter querystring in form option (@charlespwd) +- [10246c8](https://github.com/mikeal/request/commit/10246c84819db14b32fccca040029b06449242a3) [PATCH v2] Add support for gzip content decoding (@kevinoid) +- [6180c5f](https://github.com/mikeal/request/commit/6180c5f45c01fb2158b9a44f894a34263479fa84) check for content-length header before setting it in nextTick (@camilleanne) +- [#951](https://github.com/mikeal/request/pull/951) Add support for gzip content decoding (@kevinoid) +- [849c681](https://github.com/mikeal/request/commit/849c681846ce3b5492bd47261de391377a3ac19b) Silence EventEmitter memory leak warning #311 (@watson) +- [#955](https://github.com/mikeal/request/pull/955) check for content-length header before setting it in nextTick (@camilleanne) +- [#957](https://github.com/mikeal/request/pull/957) Silence EventEmitter memory leak warning #311 (@watson) +- [c1d951e](https://github.com/mikeal/request/commit/c1d951e536bd41c957f0cade41d051c9d41d1462) Fixing for 0.8 (@mikeal) +- [4851118](https://github.com/mikeal/request/commit/48511186495888a5f0cb15a107325001ac91990e) 2.37.0 (@mikeal) + +### v2.36.1 (2014/05/19 20:59 +00:00) +- [c3914fc](https://github.com/mikeal/request/commit/c3914fcd4a74faf6dbf0fb6a4a188e871e0c51b8) 2.36.1 (@mikeal) + +### v2.36.0 (2014/05/19 20:59 +00:00) +- [76a96de](https://github.com/mikeal/request/commit/76a96de75580042aa780e9587ff7a22522119c3f) Reventing lodash merge change. (@mikeal) +- [b8bb57e](https://github.com/mikeal/request/commit/b8bb57efb17e72e2ac6d957c05c3f2570c7ba6a0) 2.36.0 (@mikeal) + +### v2.35.1 (2014/05/17 20:57 +00:00) +- [4bbd153](https://github.com/mikeal/request/commit/4bbd1532a68cadf1a88dd69c277645e9b781f364) 2.35.1 (@mikeal) + +### v2.35.0 (2014/05/17 20:57 +00:00) +- [2833da3](https://github.com/mikeal/request/commit/2833da3c3c1c34f4130ad1ba470354fc32410691) initial changelog (@lalitkapoor) +- [49319e6](https://github.com/mikeal/request/commit/49319e6c09a8a169c95a8d282c900f9fecd50371) Merge branch 'master' of https://github.com/mikeal/request into create-changelog-based-on-pull-requests (@lalitkapoor) +- [#815](https://github.com/mikeal/request/pull/815) Create changelog based on pull requests (@lalitkapoor) +- [4b6ce1a](https://github.com/mikeal/request/commit/4b6ce1ac0f79cb8fa633e281d3eb4c0cb61794e1) It appears that secureOptions is an undocumented feature to fix issues with broken server. See joynet/node #5119 (@nw) +- [#821](https://github.com/mikeal/request/pull/821) added secureOptions back (@nw) +- [eddd488](https://github.com/mikeal/request/commit/eddd4889fb1bc95c741749e79d9749aab3e103fc) Fixing #825 (@mikeal) +- [4627a7a](https://github.com/mikeal/request/commit/4627a7a14078494ded8c66c19c43efd07324cbd8) improve error reporting for invalid protocols (@FND) +- [#840](https://github.com/mikeal/request/pull/840) improve error reporting for invalid protocols (@FND) +- [#810](https://github.com/mikeal/request/pull/810) add some exposition to mpu example in README.md (@mikermcneil) +- [8a0e2d6](https://github.com/mikeal/request/commit/8a0e2d65351560858275c73505df12b537f4d001) Added support for HTTP_PROXY and HTTPS_PROXY environment variables, if the proxy option isn't already set. (@jvmccarthy) +- [f60d348](https://github.com/mikeal/request/commit/f60d348dc1840ee6d7b709efcc2b3cd1a03aef63) Fix word consistency +- [#850](https://github.com/mikeal/request/pull/850) Fix word consistency in readme (@0xNobody) +- [#809](https://github.com/mikeal/request/pull/809) upgrade tunnel-proxy to 0.4.0 (@ksato9700) +- [e86377c](https://github.com/mikeal/request/commit/e86377c0c1e7695c3997f7802175ca37f5a5113b) Won't use HTTP(S)_PROXY env var if proxy explicitly set to null. (@jvmccarthy) +- [f1bb537](https://github.com/mikeal/request/commit/f1bb537ee2440bd664ea8c445ac3a2c6e31e9932) Add support for RFC 6750 Bearer Tokens +- [ba51a26](https://github.com/mikeal/request/commit/ba51a26079ec52c0a9145fbe8b6796d46e79bb8e) Add documentation about auth.bearer (@phedny) +- [#861](https://github.com/mikeal/request/pull/861) Add support for RFC 6750 Bearer Tokens (@phedny) +- [b8ee579](https://github.com/mikeal/request/commit/b8ee5790ace95440a56074f6afe866f4662e9e88) Fix typo (@dandv) +- [#866](https://github.com/mikeal/request/pull/866) Fix typo (@dandv) +- [b292b59](https://github.com/mikeal/request/commit/b292b59fadecb35dac3bee0959c4b4b782e772e3) Clean code syntax in test-pipes.js (@tgohn) +- [f7996d5](https://github.com/mikeal/request/commit/f7996d5fcfed85e03f293a7c9739e385b64ecaad) Add test for request.pipefilter (@tgohn) +- [#869](https://github.com/mikeal/request/pull/869) Pipefilter test (@tgohn) +- [86b99b6](https://github.com/mikeal/request/commit/86b99b671a3c86f4f963a6c67047343fd8edae8f) Fix typo in form example (@mscdex) +- [2ba4808](https://github.com/mikeal/request/commit/2ba48083ddf2607f85e2c479e0d254483c2610fe) failing test (@lalitkapoor) +- [39396b0](https://github.com/mikeal/request/commit/39396b0bb2e90eb7ec4dfcf5d2e731a2cb156f5c) extend passed in options (@lalitkapoor) +- [#891](https://github.com/mikeal/request/pull/891) fixes 857 - options object is mutated by calling request (@lalitkapoor) +- [54a51c6](https://github.com/mikeal/request/commit/54a51c665887e162ccb9f6b17b9c1f3b017ccc29) merge options (@vohof) +- [25b95db](https://github.com/mikeal/request/commit/25b95dbdddf874f014386a0a9fe35a7c903b7415) tilde? (@vohof) +- [#897](https://github.com/mikeal/request/pull/897) merge with default options (@vohof) +- [a1e4b1a](https://github.com/mikeal/request/commit/a1e4b1a9c2f39ce565fd023bb604da139f689d43) Fixes #555 (@pigulla) +- [#901](https://github.com/mikeal/request/pull/901) Fixes #555 (@pigulla) +- [6498a5f](https://github.com/mikeal/request/commit/6498a5f1ae68050cfeabf8f34f75bc72b08f1805) 2.35.0 (@mikeal) + +### v2.34.1 (2014/02/18 19:35 +00:00) +- [aefea20](https://github.com/mikeal/request/commit/aefea20b215ff1a48f0d8d27dcac0186604e3b2d) 2.34.1 (@mikeal) + +### v2.34.0 (2014/02/18 19:35 +00:00) +- [46edc90](https://github.com/mikeal/request/commit/46edc902e6ffdee39038a6702021728cb9d9b8fa) simpler (@joaojeronimo) +- [#781](https://github.com/mikeal/request/pull/781) simpler isReadStream function (@joaojeronimo) +- [fe2f59f](https://github.com/mikeal/request/commit/fe2f59fdc72de5c86404e51ab6bc4e0e8ece95f2) Provide ability to override content-type when `json` option used (@vvo) +- [#785](https://github.com/mikeal/request/pull/785) Provide ability to override content-type when `json` option used (@vvo) +- [d134f01](https://github.com/mikeal/request/commit/d134f012e64702e8f4070d61504b39524e1a07ba) Adds content-length calculation when submitting forms using form-data library. This is related to issue 345. (@Juul) +- [#793](https://github.com/mikeal/request/pull/793) Adds content-length calculation when submitting forms using form-data li... (@Juul) +- [3ebf25c](https://github.com/mikeal/request/commit/3ebf25c5af1194d8f7b3a3330fe89e729532809b) adding failing test (@lalitkapoor) +- [0f57a90](https://github.com/mikeal/request/commit/0f57a90384588727a5446bb1f5bf4e0be2d85780) accept options in arguments (@lalitkapoor) +- [7fb1647](https://github.com/mikeal/request/commit/7fb164731a5aad80c6539e33eda4ad4a51bb7871) silently ignore errors when adding cookie to jar (@lalitkapoor) +- [d6b2b1c](https://github.com/mikeal/request/commit/d6b2b1c279d12cdddc6593060672d49b12e63fea) add additional header test (@lalitkapoor) +- [f29e6df](https://github.com/mikeal/request/commit/f29e6dfadc6c3a45b6190998b6608059f87f3c32) Added the Apache license to the package.json. (@keskival) +- [#802](https://github.com/mikeal/request/pull/802) Added the Apache license to the package.json. (@keskival) +- [#801](https://github.com/mikeal/request/pull/801) 794 ignore cookie parsing and domain errors (@lalitkapoor) +- [54e6dfb](https://github.com/mikeal/request/commit/54e6dfb77d57757d4006982f813ebaab9e005cd5) Rewrite UNIX Domain Socket support into 2.33.1. Add test. (@lyuzashi) +- [3eaed2f](https://github.com/mikeal/request/commit/3eaed2f2e82d9d17a583bcc54270c16a7b674206) Use setImmediate when available, otherwise fallback to nextTick (@lyuzashi) +- [746ca75](https://github.com/mikeal/request/commit/746ca757da24d5011e92e04cb00c90098a7680fd) Indent wrapped buildRequest function (@lyuzashi) +- [#516](https://github.com/mikeal/request/pull/516) UNIX Socket URL Support (@native-digital) +- [9a5b0a8](https://github.com/mikeal/request/commit/9a5b0a81eca9836f05b0192c05c0d41e79034461) initial format (@lalitkapoor) +- [9380a49](https://github.com/mikeal/request/commit/9380a49779ddb081eba5d0ee51e4396d72d52066) upgrade tunnel-proxy to 0.4.0 (@ksato9700) +- [1efea37](https://github.com/mikeal/request/commit/1efea374286c728c3c988ee2264fb44cd8c41d88) add some exposition to mpu example in README.md (@mikermcneil) +- [ba0d63a](https://github.com/mikeal/request/commit/ba0d63ae23a3fc95dfe012df0bd6c8d7e87b1df7) made the language clearer (@mikermcneil) +- [b43aa81](https://github.com/mikeal/request/commit/b43aa81789c0b8c7ae90d2b983f79dde4a125470) 2.34.0 (@mikeal) + +### v2.33.1 (2014/01/16 19:48 +00:00) +- [afcf827](https://github.com/mikeal/request/commit/afcf827559b3223c96ac1bbd19bd1e4a6d7771e3) 2.33.1 (@mikeal) + +### v2.33.0 (2014/01/16 19:48 +00:00) +- [7f1cc8f](https://github.com/mikeal/request/commit/7f1cc8ff5a8d9443e7a793f4655487e722b75b0d) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [3e43d3d](https://github.com/mikeal/request/commit/3e43d3d5175f5f18d1e97b2f5d4ca6ac6c216e4a) 2.33.0 (@mikeal) + +### v2.32.1 (2014/01/16 19:33 +00:00) +- [dd44f39](https://github.com/mikeal/request/commit/dd44f39d37daacbbeb21f9e960f13adbb44eea0a) 2.32.1 (@mikeal) + +### v2.32.0 (2014/01/16 19:33 +00:00) +- [#757](https://github.com/mikeal/request/pull/757) require aws-sign2 (@mafintosh) +- [#744](https://github.com/mikeal/request/pull/744) Use Cookie.parse (@lalitkapoor) +- [5eaee1c](https://github.com/mikeal/request/commit/5eaee1ce4008ede1df15201622ac478c892d6a8a) Upgrade tough-cookie to 0.10.0 (@stash) +- [#763](https://github.com/mikeal/request/pull/763) Upgrade tough-cookie to 0.10.0 (@stash) +- [d2489d0](https://github.com/mikeal/request/commit/d2489d0e24d9a538224f5c8c090dcdeb1f8d4969) Fixed auth error for some servers like twisted. According to rfc 2617 auth scheme token should be case-insensitive. (@bobyrizov) +- [#764](https://github.com/mikeal/request/pull/764) Case-insensitive authentication scheme (@bobyrizov) +- [cbee3d0](https://github.com/mikeal/request/commit/cbee3d04ee9f704501a64edb7b9b6d201e98494b) Use tough-cookie CookieJar sync API (@stash) +- [3eeaf6a](https://github.com/mikeal/request/commit/3eeaf6a90df7b806d91ae1e8e2f56862ece2ea33) Emit error, not cookieError (@stash) +- [#767](https://github.com/mikeal/request/pull/767) Use tough-cookie CookieJar sync API (@stash) +- [9eac534](https://github.com/mikeal/request/commit/9eac534dd11e40bba65456491cb62ad68d8f41fa) 2.32.0 (@mikeal) + +### v2.31.1 (2014/01/08 02:57 +00:00) +- [b1b5e91](https://github.com/mikeal/request/commit/b1b5e9161e149574ba5528c401a70bfadef1a98a) 2.31.1 (@mikeal) + +### v2.31.0 (2014/01/08 02:57 +00:00) +- [dd2577f](https://github.com/mikeal/request/commit/dd2577f8264d4d4b07484dec7094b72c00c8416f) Removing s3 test. (@mikeal) +- [fef5bf3](https://github.com/mikeal/request/commit/fef5bf34258e3695b61c048c683f1d4a7f99b368) Fix callback arguments documentation (@mmalecki) +- [#736](https://github.com/mikeal/request/pull/736) Fix callback arguments documentation (@mmalecki) +- [5531c20](https://github.com/mikeal/request/commit/5531c208678145ef35b06e948190be2fd6a8a1c8) updating README example: cookie jar api changed cookie module changed to tough-cookie (@emkay) +- [#741](https://github.com/mikeal/request/pull/741) README example is using old cookie jar api (@emkay) +- [9d73e5a](https://github.com/mikeal/request/commit/9d73e5a277af141a6e4fa9dbcae5d0c3b755d277) add note about JSON output body type (@iansltx) +- [#742](https://github.com/mikeal/request/pull/742) Add note about JSON output body type (@iansltx) +- [41e20a4](https://github.com/mikeal/request/commit/41e20a4d288e30101e493b383a0e4852a3271a98) Use Cookie.parse (@lalitkapoor) +- [4d09556](https://github.com/mikeal/request/commit/4d095562a5c42ffb41b0ff194e9e6f32c0f44372) updating setCookie example to make it clear that the callback is required (@emkay) +- [#745](https://github.com/mikeal/request/pull/745) updating setCookie example to make it clear that the callback is required (@emkay) +- [b7ede1d](https://github.com/mikeal/request/commit/b7ede1d56f9a2764e4bf764687b81419df817e5a) README: Markdown code highlight (@weakish) +- [#746](https://github.com/mikeal/request/pull/746) README: Markdown code highlight (@weakish) +- [#645](https://github.com/mikeal/request/pull/645) update twitter api url to v1.1 (@mick) +- [20dcd18](https://github.com/mikeal/request/commit/20dcd18ce8e3397ba7e0213da9c760b048ca5b49) require aws-sign2 (@mafintosh) +- [df2c426](https://github.com/mikeal/request/commit/df2c4264321c3db1387ddf9a945d63b9ae7d57b8) 2.31.0 (@mikeal) + +### v2.30.1 (2013/12/13 19:17 +00:00) +- [eba2d40](https://github.com/mikeal/request/commit/eba2d402fcdcf1ac878de8672b1c9f5da856dcc1) 2.30.1 (@mikeal) + +### v2.30.0 (2013/12/13 19:17 +00:00) +- [aee3819](https://github.com/mikeal/request/commit/aee38191557574ef570fd9c764af0af7072cc92a) Fix TypeError when calling request.cookie +- [#728](https://github.com/mikeal/request/pull/728) Fix TypeError when calling request.cookie (@scarletmeow) +- [628ef76](https://github.com/mikeal/request/commit/628ef768b1f52710b8eb4e14be4db69d174d1dcb) better DIGEST support (@dai-shi) +- [d919bc1](https://github.com/mikeal/request/commit/d919bc1ce97fa461c365437a0c739bbaa6b86de7) ignore null authValues (DIGEST) (@dai-shi) +- [75fc209](https://github.com/mikeal/request/commit/75fc209c5a9e6c647a04e42048c30f46c66fc103) DIGEST support: pass algoritm and opaque, add TODO items, test case for compatible mode (@dai-shi) +- [#730](https://github.com/mikeal/request/pull/730) better HTTP DIGEST support (@dai-shi) +- [937a24a](https://github.com/mikeal/request/commit/937a24a168a126f406ee8eb55eb78169ddc53497) JSHINT: Creating global 'for' variable. Should be 'for (var ...'. +- [#732](https://github.com/mikeal/request/pull/732) JSHINT: Creating global 'for' variable. Should be 'for (var ...'. (@Fritz-Lium) +- [f03be23](https://github.com/mikeal/request/commit/f03be2309bd85a89d2e3c208b2fb4be1a2b95c79) Make digest qop regex more robust (see #730) (@nylen) +- [c7d97ae](https://github.com/mikeal/request/commit/c7d97aefaebf773ce62c72e9ec656f0250b7a1e7) 2.30.0 (@mikeal) + +### v2.29.1 (2013/12/06 20:05 +00:00) +- [e0f2c41](https://github.com/mikeal/request/commit/e0f2c41bd4e15518e97dd2f4c134be51ed4cb68b) 2.29.1 (@mikeal) + +### v2.29.0 (2013/12/06 20:05 +00:00) +- [3c2cad1](https://github.com/mikeal/request/commit/3c2cad11301380f4056eb3ca4c0c124f7f7f72f5) make request.defaults(options, requester) run the requester for all methods (@jchris) +- [#727](https://github.com/mikeal/request/pull/727) fix requester bug (@jchris) +- [0c9f875](https://github.com/mikeal/request/commit/0c9f87542cd1f919751d3ed1f00208ce7705f8e7) 2.29.0 (@mikeal) + +### v2.28.1 (2013/12/04 19:42 +00:00) +- [3e6a300](https://github.com/mikeal/request/commit/3e6a300121586da81b871f759a9feec52810474a) 2.28.1 (@mikeal) + +### v2.28.0 (2013/12/04 19:42 +00:00) +- [ac26f43](https://github.com/mikeal/request/commit/ac26f43d9a8212289f92056d3029c207f755cef4) Update request.js (@wprl) +- [adc2cb6](https://github.com/mikeal/request/commit/adc2cb6721e5980e8ed667a3f558cce8c89ee6c2) Use random cnonce (@wprl) +- [ff16a9d](https://github.com/mikeal/request/commit/ff16a9daf93e01cecee7fabec64c3e1b423f7db5) Add test for random cnonce (@wprl) +- [df64c2b](https://github.com/mikeal/request/commit/df64c2bc8f691ecc6f6c214e2254bab439830b88) Restore whitespace (@wprl) +- [#630](https://github.com/mikeal/request/pull/630) Send random cnonce for HTTP Digest requests (@wprl) +- [aca5a16](https://github.com/mikeal/request/commit/aca5a169c44cc658e8310691a2ae1cfc4c2b0958) update twitter api url to v1.1 (@mick) +- [abcbadd](https://github.com/mikeal/request/commit/abcbadd1b2a113c34a37b62d36ddcfd74452850e) Test case for #304. (@diversario) +- [b8cf874](https://github.com/mikeal/request/commit/b8cf8743b66d8eee4048561a7d81659f053393c8) fix failure when running with NODE_DEBUG=request, and a test for that (@jrgm) +- [e6c7d1f](https://github.com/mikeal/request/commit/e6c7d1f6d23922480c09427d5f54f84eec60b7af) quiet, but check that stderr output has something reasonable for debug (@jrgm) +- [#659](https://github.com/mikeal/request/pull/659) fix failure when running with NODE_DEBUG=request, and a test for that (@jrgm) +- [23164e4](https://github.com/mikeal/request/commit/23164e4f33bd0837d796037c3d0121db23653c34) option.tunnel to explicitly disable tunneling (@seanmonstar) +- [#662](https://github.com/mikeal/request/pull/662) option.tunnel to explicitly disable tunneling (@seanmonstar) +- [#656](https://github.com/mikeal/request/pull/656) Test case for #304. (@diversario) +- [da16120](https://github.com/mikeal/request/commit/da16120a8f0751b305a341c012dbdcfd62e83585) Change `secureOptions' to `secureProtocol' for HTTPS request (@richarddong) +- [43d9d0a](https://github.com/mikeal/request/commit/43d9d0a76974d2c61681ddee04479d514ebfa320) add `ciphers' and `secureProtocol' to `options' in `getAgent' (@richarddong) +- [#666](https://github.com/mikeal/request/pull/666) make `ciphers` and `secureProtocol` to work in https request (@richarddong) +- [524e035](https://github.com/mikeal/request/commit/524e0356b73240409a11989d369511419526b5ed) change cookie module (@sxyizhiren) +- [#674](https://github.com/mikeal/request/pull/674) change cookie module,to tough-cookie.please check it . (@sxyizhiren) +- [e8dbcc8](https://github.com/mikeal/request/commit/e8dbcc83d4eff3c14e03bd754174e2c5d45f2872) tests: Fixed test-timeout.js events unit test (@Turbo87) +- [aed1c71](https://github.com/mikeal/request/commit/aed1c71fac0047b66a236a990a5569445cfe995d) Added Travis CI configuration file (@Turbo87) +- [#683](https://github.com/mikeal/request/pull/683) Travis CI support (@Turbo87) +- [8bfa640](https://github.com/mikeal/request/commit/8bfa6403ce03cbd3f3de6b82388bfcc314e56c61) dependencies: Set `tough-cookie` as optional dependency (@Turbo87) +- [bcc138d](https://github.com/mikeal/request/commit/bcc138da67b7e1cf29dc7d264a73d8b1d1f4b0e4) dependencies: Set `form-data` as optional dependency (@Turbo87) +- [751ac28](https://github.com/mikeal/request/commit/751ac28b7f13bfeff2a0e920ca2926a005dcb6f0) dependencies: Set `tunnel-agent` as optional dependency (@Turbo87) +- [6d7c1c9](https://github.com/mikeal/request/commit/6d7c1c9d8e3a300ff6f2a93e7f3361799acf716b) dependencies: Set `http-signature` as optional dependency (@Turbo87) +- [733f1e3](https://github.com/mikeal/request/commit/733f1e3ae042a513a18cde1c6e444b18ee07ad66) Added .npmignore file (@Turbo87) +- [e2fc346](https://github.com/mikeal/request/commit/e2fc346b7e5e470fcd36189bcadf63c53feebb22) dependencies: Set `hawk` as optional dependency (@Turbo87) +- [e87d45f](https://github.com/mikeal/request/commit/e87d45fe89ea220035bf07696a70292763f7135f) dependencies: Set `aws-sign` as optional dependency (@Turbo87) +- [1cd81ba](https://github.com/mikeal/request/commit/1cd81ba30908b77cff2fa618aeb232fefaa53ada) lib: Added optional() function (@Turbo87) +- [28c2c38](https://github.com/mikeal/request/commit/28c2c3820feab0cc719df213a60838db019f3e1a) dependencies: Set `oauth-sign` as optional dependency (@Turbo87) +- [2ceddf7](https://github.com/mikeal/request/commit/2ceddf7e793feb99c5b6a76998efe238965b22cd) TravisCI: Test with and without optional dependencies (@Turbo87) +- [#682](https://github.com/mikeal/request/pull/682) Optional dependencies (@Turbo87) +- [2afab5b](https://github.com/mikeal/request/commit/2afab5b665a2e03becbc4a42ad481bb737405655) Handle blank password in basic auth. (@diversario) +- [cabe5a6](https://github.com/mikeal/request/commit/cabe5a62dc71282ce8725672184efe9d97ba79a5) Handle `auth.password` and `auth.username`. (@diversario) +- [#690](https://github.com/mikeal/request/pull/690) Handle blank password in basic auth. (@diversario) +- [33100c3](https://github.com/mikeal/request/commit/33100c3c7fa678f592374f7b2526fe9a0499b6f6) Typo (@VRMink) +- [#694](https://github.com/mikeal/request/pull/694) Typo in README (@ExxKA) +- [9072ff1](https://github.com/mikeal/request/commit/9072ff1556bcb002772838a94e1541585ef68f02) Edited README.md for formatting and clarity of phrasing (@Zearin) +- [#696](https://github.com/mikeal/request/pull/696) Edited README.md for formatting and clarity of phrasing (@Zearin) +- [07ee58d](https://github.com/mikeal/request/commit/07ee58d3a8145740ba34cc724f123518e4b3d1c3) Fixing listing in callback part of docs. (@lukasz-zak) +- [#710](https://github.com/mikeal/request/pull/710) Fixing listing in callback part of docs. (@lukasz-zak) +- [8ee21d0](https://github.com/mikeal/request/commit/8ee21d0dcc637090f98251eba22b9f4fd1602f0e) Request.multipart no longer crashes when header 'Content-type' is present (@pastaclub) +- [#715](https://github.com/mikeal/request/pull/715) Request.multipart no longer crashes when header 'Content-type' present (@pastaclub) +- [8b04ca6](https://github.com/mikeal/request/commit/8b04ca6ad8d025c275e40b806a69112ac53bd416) doc: Removed use of gendered pronouns (@oztu) +- [#719](https://github.com/mikeal/request/pull/719) Made a comment gender neutral. (@oztu) +- [8795fc6](https://github.com/mikeal/request/commit/8795fc68cce26b9a45d10db9eaffd4bc943aca3a) README.md: add custom HTTP Headers example. (@tcort) +- [#724](https://github.com/mikeal/request/pull/724) README.md: add custom HTTP Headers example. (@tcort) +- [c5d5b1f](https://github.com/mikeal/request/commit/c5d5b1fcf348e768943fe632a9a313d704d35c65) Changing dep. (@mikeal) +- [bf04163](https://github.com/mikeal/request/commit/bf04163883fa9c62d4e1a9fdd64d6efd7723d5f8) 2.28.0 (@mikeal) + +### v2.27.1 (2013/08/15 21:30 +00:00) +- [a80a026](https://github.com/mikeal/request/commit/a80a026e362a9462d6948adc1b0d2831432147d2) 2.27.1 (@mikeal) + +### v2.27.0 (2013/08/15 21:30 +00:00) +- [3627b9c](https://github.com/mikeal/request/commit/3627b9cc7752cfe57ac609ed613509ff61017045) rename Request and remove .DS_Store (@joaojeronimo) +- [920f9b8](https://github.com/mikeal/request/commit/920f9b88f7dd8f8d153e72371b1bf2d16d5e4160) rename Request (@joaojeronimo) +- [c243cc6](https://github.com/mikeal/request/commit/c243cc66131216bb57bcc0fd79c250a7927ee424) for some reason it removed request.js (@joaojeronimo) +- [#619](https://github.com/mikeal/request/pull/619) decouple things a bit (@CrowdProcess) +- [ed4ecc5](https://github.com/mikeal/request/commit/ed4ecc5ae5cd1d9559a937e84638c9234244878b) Try normal stringify first, then fall back to safe stringify (@mikeal) +- [5642ff5](https://github.com/mikeal/request/commit/5642ff56e64c19e8183dcd5b6f9d07cca295a79e) 2.27.0 (@mikeal) + +### v2.26.1 (2013/08/07 16:31 +00:00) +- [b422510](https://github.com/mikeal/request/commit/b422510ba16315c3e0e1293a17f3a8fa7a653a77) 2.26.1 (@mikeal) + +### v2.26.0 (2013/08/07 16:31 +00:00) +- [3b5b62c](https://github.com/mikeal/request/commit/3b5b62cdd4f3b92e63a65d3a7265f5a85b11c4c9) Only include :password in Basic Auth if it's defined (fixes #602) (@bendrucker) +- [#605](https://github.com/mikeal/request/pull/605) Only include ":" + pass in Basic Auth if it's defined (fixes #602) (@bendrucker) +- [cce2c2c](https://github.com/mikeal/request/commit/cce2c2c8ea5b0136932b2432e4e25c0124d58d5a) Moved init of self.uri.pathname (@lexander) +- [08793ec](https://github.com/mikeal/request/commit/08793ec2f266ef88fbe6c947e6b334e04d4b9dc9) Fix all header casing issues forever. (@mikeal) +- [#613](https://github.com/mikeal/request/pull/613) Fixes #583, moved initialization of self.uri.pathname (@lexander) +- [f98ff99](https://github.com/mikeal/request/commit/f98ff990d294165498c9fbf79b2de12722e5c842) Update this old ass readme with some new HOTNESS! (@mikeal) +- [3312010](https://github.com/mikeal/request/commit/3312010f72d035f22b87a6d8d463f0d91b88fea1) markdown badge instead. (@mikeal) +- [9cf657c](https://github.com/mikeal/request/commit/9cf657c1f08bf460911b8bb0a8c5c0d3ae6135c7) Shorter title. (@mikeal) +- [2c61d66](https://github.com/mikeal/request/commit/2c61d66f1dc323bb612729c7320797b79b22034c) put Request out (@joaojeronimo) +- [28513a1](https://github.com/mikeal/request/commit/28513a1b371452699438c0eb73471f8969146264) 2.26.0 (@mikeal) + +### v2.25.1 (2013/07/23 21:51 +00:00) +- [6387b21](https://github.com/mikeal/request/commit/6387b21a9fb2e16ee4dd2ab73b757eca298587b5) 2.25.1 (@mikeal) + +### v2.25.0 (2013/07/23 21:51 +00:00) +- [828f12a](https://github.com/mikeal/request/commit/828f12a1ae0f187deee4d531b2eaf7531169aaf2) 2.25.0 (@mikeal) + +### v2.24.1 (2013/07/23 20:51 +00:00) +- [29ae1bc](https://github.com/mikeal/request/commit/29ae1bc454c03216beeea69d65b538ce4f61e8c1) 2.24.1 (@mikeal) + +### v2.24.0 (2013/07/23 20:51 +00:00) +- [f667318](https://github.com/mikeal/request/commit/f66731870d5f3e0e5655cd89612049b540c34714) Fixed a small typo (@michalstanko) +- [#601](https://github.com/mikeal/request/pull/601) Fixed a small typo (@michalstanko) +- [#594](https://github.com/mikeal/request/pull/594) Emit complete event when there is no callback (@RomainLK) +- [#596](https://github.com/mikeal/request/pull/596) Global agent is being used when pool is specified (@Cauldrath) +- [41ce492](https://github.com/mikeal/request/commit/41ce4926fb08242f19135fd3ae10b18991bc3ee0) New deps. (@mikeal) +- [8176c94](https://github.com/mikeal/request/commit/8176c94d5d17bd14ef4bfe459fbfe9cee5cbcc6f) 2.24.0 (@mikeal) + +### v2.23.1 (2013/07/23 02:45 +00:00) +- [63f31cb](https://github.com/mikeal/request/commit/63f31cb1d170a4af498fbdd7566f867423caf8e3) 2.23.1 (@mikeal) + +### v2.23.0 (2013/07/23 02:44 +00:00) +- [758f598](https://github.com/mikeal/request/commit/758f598de8d6024db3fa8ee7d0a1fc3e45c50f53) Initial commit. Request package. (@mikeal) +- [104cc94](https://github.com/mikeal/request/commit/104cc94839d4b71aaf3681142daefba7ace78c94) Removing unnecessary markup. (@mikeal) +- [12a4cb8](https://github.com/mikeal/request/commit/12a4cb88b949cb4a81d51189d432c25c08522a87) Matching node documentation style. (@mikeal) +- [ab96993](https://github.com/mikeal/request/commit/ab969931106b10b5f8658dc9e0f512c5dfc2a7da) Release tarball. (@mikeal) +- [e7e37ad](https://github.com/mikeal/request/commit/e7e37ad537081a040ea3e527aac23ae859b40b2c) Removing old tarball. (@mikeal) +- [e66e90d](https://github.com/mikeal/request/commit/e66e90dd814ae7bfbcd52003609d7bde9eafea57) Adding automatic redirect following. (@mikeal) +- [2fc5b84](https://github.com/mikeal/request/commit/2fc5b84832ae42f6ddb081b1909d0a6ca00c8d51) Adding SSL support. (@mikeal) +- [a3ac375](https://github.com/mikeal/request/commit/a3ac375d4b5800a038ae26233425fadc26866fbc) Fixing bug where callback fired for every redirect. (@mikeal) +- [1139efe](https://github.com/mikeal/request/commit/1139efedb5aad4a328c1d8ff45fe77839a69169f) Cleaning up tests. (@mikeal) +- [bb49fe6](https://github.com/mikeal/request/commit/bb49fe6709fa06257f4b7aadc2e450fd45a41328) Rolling version. (@mikeal) +- [4ff3493](https://github.com/mikeal/request/commit/4ff349371931ec837339aa9082c4ac7ddd4c7c35) Updates to README.md (@mikeal) +- [1c9cf71](https://github.com/mikeal/request/commit/1c9cf719c92b02ba85c4e47bd2b92a3303cbe1cf) Adding optional body buffer. (@mikeal) +- [49dfef4](https://github.com/mikeal/request/commit/49dfef42630c4fda6fb208534c00638dc0f06a6b) Rolling version. (@mikeal) +- [ab40cc8](https://github.com/mikeal/request/commit/ab40cc850652e325fcc3b0a44ee7303ae0a7b77f) Preserve the original full path. (@mikeal) +- [6d70f62](https://github.com/mikeal/request/commit/6d70f62c356f18098ca738b3dbedcf212ac3d8d8) Rolling version. (@mikeal) +- [e2ca15a](https://github.com/mikeal/request/commit/e2ca15a0f7e986e3063977ee9bd2eb69e86bdb1f) Fixing bugs and rolling version. (@mikeal) +- [8165254](https://github.com/mikeal/request/commit/81652543d3a09553cbf33095a7932dec53ccecc2) Cleanup. Fixing '' === '/' path bug. (@mikeal) +- [a0536a4](https://github.com/mikeal/request/commit/a0536a46d0b91e204fbde1e4341461bc827c9542) Rolling version. (@mikeal) +- [9ccaad7](https://github.com/mikeal/request/commit/9ccaad7dce05e5dcc3eacaf1500404622a0d8067) Adding stream support for request and response bodies. (@mikeal) +- [585166d](https://github.com/mikeal/request/commit/585166d979d4476e460e9835cc0516d04a9a3e11) Rolling version. (@mikeal) +- [41111c8](https://github.com/mikeal/request/commit/41111c88d711da80ea123df238d62038b89769bf) Bugfix release for response stream. (@mikeal) +- [86e375d](https://github.com/mikeal/request/commit/86e375d093700affe4d6d2b76a7acedbe8da140c) Remove host header when we add it. (@mikeal) +- [3a6277c](https://github.com/mikeal/request/commit/3a6277c81cfd3457c760f2aaea44852ef832a1e8) Rolling version. (@mikeal) +- [7a11f69](https://github.com/mikeal/request/commit/7a11f69d5353ecc1319e2e91ca4aefbaf0338136) writing requestBodyStream into request (@beanieboi) +- [186e9cf](https://github.com/mikeal/request/commit/186e9cf692511d768f8016d311609a0a0a315af6) Using sys.pump (@mikeal) +- [09e7ade](https://github.com/mikeal/request/commit/09e7ade541e1d40316a3f153128871a353e707b1) Fixing host port addition. Rolling version. (@mikeal) +- [cec3f3f](https://github.com/mikeal/request/commit/cec3f3f619322f27e2a82c7fd8971722f98d04d6) Using builtin base64. (@mikeal) +- [2a2e2a2](https://github.com/mikeal/request/commit/2a2e2a2f5c4760d4da3caa1a0f2d14c31a4222dc) new structure. new convenience methods (@mikeal) +- [f835b5f](https://github.com/mikeal/request/commit/f835b5fb605506b8ecd3c17bebe9ed54f0066cfc) removing old files. (@mikeal) +- [91616c4](https://github.com/mikeal/request/commit/91616c4e4f488f75a8b04b5b6f0ceef7e814cffd) Adding better redirect handling. (@mikeal) +- [3a95433](https://github.com/mikeal/request/commit/3a95433cbec9693a16ff365148489a058720ae7c) Fixing tests. (@mikeal) +- [38eb1d2](https://github.com/mikeal/request/commit/38eb1d2fa8dea582bb7c3fb37a7b05ff91857a46) By popular demand, proxy support! Not really tested yet but it seems to kinda work. (@mikeal) +- [45d41df](https://github.com/mikeal/request/commit/45d41dff63f36b25b3403e59c8b172b7aa9ed373) Added proxy auth. (@mikeal) +- [85e3d97](https://github.com/mikeal/request/commit/85e3d97e0dced39a3769c4e3f2707ba3aaab1eaa) Fixing for non-proxy case. (@mikeal) +- [f796da7](https://github.com/mikeal/request/commit/f796da74849d2b0732bd1bae1d2dcaf1243142c1) Fixing relative uri's for forwards. (@mikeal) +- [dead30e](https://github.com/mikeal/request/commit/dead30ebef9c3ff806b895e2bd32f52ba3988c69) Adding support for specifying an encoding for the response body. (@mikeal) +- [9433344](https://github.com/mikeal/request/commit/943334488dcc8e7f90727b86f9eb1bc502c33b4f) Removing debugging statement (@mikeal) +- [41efb7a](https://github.com/mikeal/request/commit/41efb7a7dcca3b47e97c23c6cdbd3e860d3bd82b) Error on maxRedirects exceeded. (@mikeal) +- [9549570](https://github.com/mikeal/request/commit/95495701fa4e99a3ab85acdab71ecdaabe0dbd45) Allow options.url, people do it all the time, might as well just support it. (@mikeal) +- [21a53c0](https://github.com/mikeal/request/commit/21a53c016edcc113e809219639807b46d29dba36) Pumping version. (@mikeal) +- [aca9782](https://github.com/mikeal/request/commit/aca9782285fe1d727570fe8d799561f45d49048e) Fixing byteLength !== string lenght issues. (@mikeal) +- [a77c296](https://github.com/mikeal/request/commit/a77c296431eda2a211f59bdb88654c4a64ed4ef3) Don't rely on automatic semicolon insertion (pretty please :) (@papandreou) +- [8b02f29](https://github.com/mikeal/request/commit/8b02f29c9019dd1d1dd291dd85889b26f592a137) Also set content-length when options.body is the empty string. (@papandreou) +- [023281c](https://github.com/mikeal/request/commit/023281ca9b4414a9bc0170c2b08aaf886a7a08f7) Simplified boolean logic. (@papandreou) +- [4f897fd](https://github.com/mikeal/request/commit/4f897fdd6c7c93bea73dbf34623f09af63bb1ed4) Simplified check for whether response.headers.location starts with "http:" or "https:". (@papandreou) +- [6d7db85](https://github.com/mikeal/request/commit/6d7db85cadf401dffdec07a4d66822207898c69e) Fixed double var declaration. (@papandreou) +- [97255cf](https://github.com/mikeal/request/commit/97255cfd2a4aa8f34d307e7cd96fe1c1f13cb26a) Process redirects as soon as the response arrives. Prevents the uninteresting redirect response from being pumped into responseBodyStream. (@papandreou) +- [b2af15f](https://github.com/mikeal/request/commit/b2af15f4fcbe1115cf8b53c5ae89fbf2365bfffc) New feature: If options.noBuffer is true, don't buffer up the response, just return it. Most of the time getting a readable stream is much more flexible than having the option to pipe the response into a writable stream. For one thing, the stream can be paused. (@papandreou) +- [fee5f89](https://github.com/mikeal/request/commit/fee5f89159a8f36b25df509c55093bf7ebd1c993) A few fixes/changes from papandreou's code, also added new semantics for onResponse. (@mikeal) +- [fa72fcb](https://github.com/mikeal/request/commit/fa72fcb950029b222f0621e2d49304e35d08c380) Updated documentation. (@mikeal) +- [4fc7209](https://github.com/mikeal/request/commit/4fc72098e7eeb9518951b9306115340ffdcce7ce) Fix for both onResponse and callback (@mikeal) +- [3153436](https://github.com/mikeal/request/commit/3153436404fca865a65649d46eb22d9797128c9d) Adding license information. (@mikeal) +- [59570de](https://github.com/mikeal/request/commit/59570dec37913c7e530303a83f03781d9aca958c) Fix for unescaping passwords for basic auth. (@notmatt) +- [0d771ab](https://github.com/mikeal/request/commit/0d771ab7882b97d776179972c51c59386f91b953) require querystring (@notmatt) +- [875f79b](https://github.com/mikeal/request/commit/875f79b6a40340457fafafdadac813cfa5343689) Allow request's body to be an object. (@Stanley) +- [86895b9](https://github.com/mikeal/request/commit/86895b9c37f7b412b7df963c2a75361ff402d8c5) Merge branch 'master' of github.com:Stanley/request (@Stanley) +- [4c9c984](https://github.com/mikeal/request/commit/4c9c984cb37bfd4e901ce24b0e9b283604c27bf4) Better tests. (@mikeal) +- [02f6b38](https://github.com/mikeal/request/commit/02f6b38c1697a55ed43940d1fd0bef6225d4faa2) Added specs for body option (@Stanley) +- [af66607](https://github.com/mikeal/request/commit/af666072a22b8df4d75fe71885139059f56ea5ee) Made specs pass (@Stanley) +- [641ec05](https://github.com/mikeal/request/commit/641ec052dd95797816e781b2c3ac2524841db7cb) Merge branch 'master' of https://github.com/Stanley/request into jsonbody (@mikeal) +- [ab4c96b](https://github.com/mikeal/request/commit/ab4c96be1c002c10806d967a4b266543f8b0267c) Moved spec tests to normal node script tests. Style changes to code and docs. (@mikeal) +- [fc2a7ef](https://github.com/mikeal/request/commit/fc2a7ef301c1266938a5aeb539e4f3fc3b5191dd) Clearer wording for json option. (@mikeal) +- [01371d7](https://github.com/mikeal/request/commit/01371d728082e22aabeb840da82a30aec62d7d8a) Removing specs loader. (@mikeal) +- [560dadd](https://github.com/mikeal/request/commit/560dadd6cbd293622c66cd82b5506704c9850b13) Adding newline to end of test files, makes for cleaner diffs in the future. (@mikeal) +- [a0348dd](https://github.com/mikeal/request/commit/a0348dd0fef462c3c678a639619c27101c757035) Add pass message when tests finish. (@mikeal) +- [da77a0e](https://github.com/mikeal/request/commit/da77a0e152c1dd43f5c1e698110d23e4d32280db) Adding better debug message on failures for GET tests. (@mikeal) +- [6aade82](https://github.com/mikeal/request/commit/6aade822a90724a47176771d137e30b0a702e7ef) throw on error. (@mikeal) +- [4f41b8d](https://github.com/mikeal/request/commit/4f41b8dbbf9a93c53d5ccdf483c9d7803e279916) Rolling version. (@mikeal) +- [7cf01f0](https://github.com/mikeal/request/commit/7cf01f0481afb367b5d0d4878645ac535cfe9a2e) master is moving to node v0.3.6+ (@mikeal) +- [cb403a4](https://github.com/mikeal/request/commit/cb403a4cfdbe3d98feb9151fdbdae1e1436e59ab) Initial support for 0.3.6+.\n\nExperimental support for Request objects as streams. It's untested and requires a pending patch to node.js (@mikeal) +- [a3c80f9](https://github.com/mikeal/request/commit/a3c80f98f42f25d4cb02d5d9e34ba0e67cc89293) Adding defaults call. (@mikeal) +- [55f22f9](https://github.com/mikeal/request/commit/55f22f96365c57aa8687de951e3f9ed982eba408) Request will keep it's own agent pool so that it can expose a maxSockets setting for easy pool sizing. (@mikeal) +- [004741c](https://github.com/mikeal/request/commit/004741c23dc0eaf61f111161bb913ba418e033e4) Fixing reference error. (@mikeal) +- [8548541](https://github.com/mikeal/request/commit/85485414150fbac58b08126b3684f81dcb930bf1) Simplified pool implementation. (@mikeal) +- [9121c47](https://github.com/mikeal/request/commit/9121c47e4cbe47bccc20a75e0e6c6c098dce04fb) Default to globalPool. (@mikeal) +- [9ec3490](https://github.com/mikeal/request/commit/9ec3490aefd52f05b57e6db13730ace54b4439d1) Support for https. Requires pending patch in node core for consistent Agent API. (@mikeal) +- [146b154](https://github.com/mikeal/request/commit/146b154a1a31ae7a30aa9f28e891e4824af548fa) Fixes for reference errors. (@mikeal) +- [8756120](https://github.com/mikeal/request/commit/8756120f83ceb94f8ba600acba274ba512696eef) Only create an agent when a relevant option is passed. (@mikeal) +- [cc3cf03](https://github.com/mikeal/request/commit/cc3cf0322847982875ff32a7cef25c39c29630ba) New HTTP client doesn't require such explicit error listener management. (@mikeal) +- [f7c0379](https://github.com/mikeal/request/commit/f7c0379b99ac7989df7f934be67cc3ae979591bb) Fixing bug in .pipe() handling. Thanks tanepiper. (@mikeal) +- [897a7ef](https://github.com/mikeal/request/commit/897a7ef020cefcb7a36c04a11e286238df8ecdaa) Fixes for streams, docs, and convenience methods. (@mikeal) +- [7c2899a](https://github.com/mikeal/request/commit/7c2899a046b750eda495b23b2d58604260deddbc) Doc fixes. (@mikeal) +- [f535fe1](https://github.com/mikeal/request/commit/f535fe1008c8f11bb37e16f95fe287ed93343704) Doc fixes. (@mikeal) +- [d1deb5b](https://github.com/mikeal/request/commit/d1deb5b4dda4474fe9d480ad42ace664d89e73ee) Pipe tests, all passing! (@mikeal) +- [d67a041](https://github.com/mikeal/request/commit/d67a041783df8d724662d82f9fb792db1be3f4f0) Moving basic example to the top. (@mikeal) +- [6a98b9e](https://github.com/mikeal/request/commit/6a98b9e4a561b516b14d325c48785a9d6f40c514) Do not mix encoding option with pipeing. (@mikeal) +- [06b67ef](https://github.com/mikeal/request/commit/06b67ef01f73572a6a9b586854d4c21be427bdb2) Disable pooling with {pool:false} (@mikeal) +- [1c24881](https://github.com/mikeal/request/commit/1c248815b5dfffda43541e367bd4d66955ca0325) Send all arguments passed to stream methods. (@mikeal) +- [7946393](https://github.com/mikeal/request/commit/7946393893e75df24b390b7ab19eb5b9d6c23891) Better errors and warnings for different pipe conditions. (@mikeal) +- [ee2108d](https://github.com/mikeal/request/commit/ee2108db592113a0fe3840c361277fdd89f0c89c) Removing commented out legacy code. (@mikeal) +- [5f838b3](https://github.com/mikeal/request/commit/5f838b3582eda465f366d7df89c6dd69920405f2) Fixing redirect issue, thanks @linus (@mikeal) +- [c08758e](https://github.com/mikeal/request/commit/c08758e25290ee12278b3eb95d502645e0d66e4e) Adding del alias, thanks tanepiper. (@mikeal) +- [0b7d675](https://github.com/mikeal/request/commit/0b7d6756c120ebf17ce6c70fc1ff4ecd6850e704) Keep require('https') from throwing if node is compiled with --without-ssl. This will still throw for Invalid Protocol if https is used. Which makes more sense and makes request work without SSl support. (@davglass) +- [02fc9f7](https://github.com/mikeal/request/commit/02fc9f7cc8912402a5a98ddefaffa5f6da870562) Rolling version. Pushed new version to npm. (@mikeal) +- [0b30532](https://github.com/mikeal/request/commit/0b30532ee1a3cabb177017acfa7885b157031df2) Sent a patch today to fix this in core but this hack will fix node that predates that fix to core. (@mikeal) +- [5d5d8f4](https://github.com/mikeal/request/commit/5d5d8f43156b04fd3ceb312cfdf47cc2b0c4104d) Rolling version. Pushed new version to npm. (@mikeal) +- [1c00080](https://github.com/mikeal/request/commit/1c000809f1795d2e21635a626cf730aba2049d3e) Fixing reference to tls. (@mikeal) +- [4c355d1](https://github.com/mikeal/request/commit/4c355d1f87fced167e4b21770bfe6f8208f32b53) Be a better stream. (@mikeal) +- [9bed22f](https://github.com/mikeal/request/commit/9bed22f22e007201d4faeebdb486603c3bb088c3) Rolled version and pushed to npm (@mikeal) +- [34df8e2](https://github.com/mikeal/request/commit/34df8e2301dcfd10705b9ff3b257741b0816c8a1) typo in `request.defaults` (@clement) +- [4d7a6d4](https://github.com/mikeal/request/commit/4d7a6d46fa481e43fe873b8c8fad2f7dd816dbb5) default value only if undefined in `request.defaults` + misplaced `return` statement (@clement) +- [243a565](https://github.com/mikeal/request/commit/243a56563f1014318a467e46113b2c61b485f377) Adding support for request(url) (@mikeal) +- [83a9cec](https://github.com/mikeal/request/commit/83a9cec3cb2f7a43a1e10c13da8d0dd72b937965) Fixing case where + is in user or password. (@mikeal) +- [8bb7f98](https://github.com/mikeal/request/commit/8bb7f98ba8b78c217552c979811c07f1299318fe) making Request a duplex stream rather than adding special handling for pipes out. (@mikeal) +- [55a1fde](https://github.com/mikeal/request/commit/55a1fdedcad1e291502ce10010dda7e478a1b503) pause and resume should act on response instead of request (@tobowers) +- [63125a3](https://github.com/mikeal/request/commit/63125a33523e72e449ceef76da57b63522998282) Making request really smart about pipeing to itself so that we can do simple proxy cats (@mikeal) +- [2f9e257](https://github.com/mikeal/request/commit/2f9e257bc39eb329eec660c6d675fb40172fc5a5) Rolling version since master right now has some pretty hot new code in it. (@mikeal) +- [#31](https://github.com/mikeal/request/pull/31) Error on piping a request to a destination (@tobowers) +- [b1f3d54](https://github.com/mikeal/request/commit/b1f3d5439d24b848b2bf3a6459eea74cb0e43df3) The "end" event that was supposed to be emitted to fix a core bug in NodeJS wasn't fired because it wasn't emitted on the response object. (@voxpelli) +- [#35](https://github.com/mikeal/request/pull/35) The "end" event isn't emitted for some responses (@voxpelli) +- [40b1c67](https://github.com/mikeal/request/commit/40b1c676e1d3a292719ad2dd9cf9354c101bad47) Rolling version. (@mikeal) +- [9a28022](https://github.com/mikeal/request/commit/9a28022d0e438d0028e61a53e897689470025e50) Fixing bug in forwarding with new pipes logic. (@mikeal) +- [44e4e56](https://github.com/mikeal/request/commit/44e4e5605b0a9e02036393bcbd3a8d91280f5611) Fixing big bug in forwarding logic. (@mikeal) +- [b0cff72](https://github.com/mikeal/request/commit/b0cff72d63689d96e0b1d49a8a5aef9ccc71cb8b) Added timeout option to abort the request before the response starts responding (@mbrevoort) +- [cc76b10](https://github.com/mikeal/request/commit/cc76b109590437bfae54116e3424b2c6e44a3b3e) corrected spelling error in README (@mbrevoort) +- [#45](https://github.com/mikeal/request/pull/45) Added timeout option (@mbrevoort) +- [1cca56b](https://github.com/mikeal/request/commit/1cca56b29bb670c53d5995e76c0b075a747b5ad7) Fixing for node http client refactor. (@mikeal) +- [2a78aa3](https://github.com/mikeal/request/commit/2a78aa3f827e76c548e001fa519448b24466b518) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [ce12273](https://github.com/mikeal/request/commit/ce12273d3990c1446d3166bbd9e35c0e2435f137) New fs.ReadStream handling hotness. (@mikeal) +- [535e30a](https://github.com/mikeal/request/commit/535e30a4bd4a8e41d97ffa6a4e99630ac09a4bcb) Adding pipe support to HTTP ServerResponse objects. (@mikeal) +- [2f0cf6b](https://github.com/mikeal/request/commit/2f0cf6bf44edbaec4c0a0cb15a679302de7f0aff) Setting proper statusCode. (@mikeal) +- [6e3ecb1](https://github.com/mikeal/request/commit/6e3ecb106c3a32101d80ac0f87968fddd3ac5e2c) Adding test for pipeing file to disc. (@mikeal) +- [bbbb52e](https://github.com/mikeal/request/commit/bbbb52e406b65100b557caa3687a1aa04fab6ff3) Pumping version. (@mikeal) +- [a10b6e4](https://github.com/mikeal/request/commit/a10b6e4c08478364b8079801fdb23f3530fcc85f) Adding reference to Request instance on response to make it easier on inline callbacks. fixes #43. (@mikeal) +- [b9aff1f](https://github.com/mikeal/request/commit/b9aff1fe007dab3f93e666f047fa03a4e8f5f8b7) Add body property to resp when we have it as a shorthand. fixes #28 (@mikeal) +- [411b30d](https://github.com/mikeal/request/commit/411b30dab1fe5b20880113aa801a2fdbb7c35c40) If the error is handled and not throw we would still process redirects. Fixes #34. (@mikeal) +- [8f3c2b4](https://github.com/mikeal/request/commit/8f3c2b4f6dee8838f30e2430a23d5071128148f0) w00t! request 2.0 (@mikeal) +- [9957542](https://github.com/mikeal/request/commit/9957542cc6928443f3a7769510673665b5a90040) valid semver. (@mikeal) +- [31f5ee2](https://github.com/mikeal/request/commit/31f5ee28726ac7e14355cad0c6d2785f9ca422c6) Drastically improved header handling. (@mikeal) +- [c99b8fc](https://github.com/mikeal/request/commit/c99b8fcd706ae035f6248669b017ac2995e45f31) Return destination stream from pipe(). (@mikeal) +- [cba588c](https://github.com/mikeal/request/commit/cba588cec1e204d70f40f8bd11df0e27dc78ef0c) Style fixes. Bye Bye semi-colons. Mostly lined up with npm style. (@mikeal) +- [8515a51](https://github.com/mikeal/request/commit/8515a510ccc0a661d7c28fce6e513a7d71be7f8f) Clearer spacing. Slightly more consistent. (@mikeal) +- [3acd82a](https://github.com/mikeal/request/commit/3acd82a10e7d973fc5dbaa574c2e8906e48e1ee9) add failing test for issue #51 (@benatkin) +- [68c17f6](https://github.com/mikeal/request/commit/68c17f6c9a3d7217368b3b8bc61203e6a14eb4f0) implement parsing json response when json is truthy (@benatkin) +- [1cb1ec1](https://github.com/mikeal/request/commit/1cb1ec114b03394a0a530f245a857d8424cad02d) allow empty string (@benatkin) +- [4f8d2df](https://github.com/mikeal/request/commit/4f8d2df9f845690667a56e7698dbaf23b5028177) support JSON APIs that don't set the write content type (@benatkin) +- [#53](https://github.com/mikeal/request/pull/53) Parse json: Issue #51 (@benatkin) +- [c63e6e9](https://github.com/mikeal/request/commit/c63e6e96378a2b050bddbe1b39337662f304dc95) Adding proxy to docs, don't know why this wasn't already in. (@mikeal) +- [ef767d1](https://github.com/mikeal/request/commit/ef767d12f13a9c78d3df89add7556f5421204843) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [1b12d3a](https://github.com/mikeal/request/commit/1b12d3a9f48a6142d75fa1790c80eb313388ca44) Emit a proper error. (@mikeal) +- [47314d7](https://github.com/mikeal/request/commit/47314d7cb41fe9c3a7717a502bed9cf1b6074ffc) Greatly expanded documentation. (@mikeal) +- [e477369](https://github.com/mikeal/request/commit/e477369b4bbc271248ee8b686c556567570a6cca) Doc refinements. (@mikeal) +- [fe4d221](https://github.com/mikeal/request/commit/fe4d22109bc1411c29b253756d609856327ff146) Fix for newer npm (@mikeal) +- [7b2f788](https://github.com/mikeal/request/commit/7b2f788293e205edc7b46a7fd5304296b5e800e3) More doc cleanup. (@mikeal) +- [f8eb2e2](https://github.com/mikeal/request/commit/f8eb2e229aca38547236d48066a0b3f9f8f67638) Copy headers so that they survive mutation. (@mikeal) +- [59eab0e](https://github.com/mikeal/request/commit/59eab0e5e49c6d32697822f712ed725843e70010) Rolling version. (@mikeal) +- [76bf5f6](https://github.com/mikeal/request/commit/76bf5f6c6e37f6cb972b3d4f1ac495a4ceaaa00d) Improvements to json handling and defaults. (@mikeal) +- [81e2c40](https://github.com/mikeal/request/commit/81e2c4040a9911a242148e1d4a482ac6c745d8eb) Rolling version. (@mikeal) +- [76d8924](https://github.com/mikeal/request/commit/76d8924cab295f80518a71d5903f1e815618414f) Proper checking and handling of json bodies (@mikeal) +- [a8422a8](https://github.com/mikeal/request/commit/a8422a80895ed70e3871c7826a51933a75c51b69) Rolling version. (@mikeal) +- [f236376](https://github.com/mikeal/request/commit/f2363760782c3d532900a86d383c34f3c94f6d5f) Adding pipefilter. (@mikeal) +- [dd85f8d](https://github.com/mikeal/request/commit/dd85f8da969c2cc1825a7dfec6eac430de36440c) Rolling version. (@mikeal) +- [#66](https://github.com/mikeal/request/pull/66) Do not overwrite established content-type headers for read stream deliver (@voodootikigod) +- [b09212f](https://github.com/mikeal/request/commit/b09212f38fe736c2c92a1ee076cae9d0f4c612c3) Do not overwrite established content-type headers for read stream deliveries. (@voodootikigod) +- [01bc25d](https://github.com/mikeal/request/commit/01bc25d25343d73e9f5731b3d0df1cf5923398d4) Only apply workaround on pre-0.5 node.js and move test to assert.equal (@mikeal) +- [d487131](https://github.com/mikeal/request/commit/d487131ebc2f7a4bf265061845f7f3ea2fd3ed34) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [1200df5](https://github.com/mikeal/request/commit/1200df52bd334f9a44a43846159146b8f938fd9e) Rolling version. (@mikeal) +- [8279362](https://github.com/mikeal/request/commit/82793626f6965884a3720d66f5a276d7d4d30873) fix global var leaks (@aheckmann) +- [#67](https://github.com/mikeal/request/pull/67) fixed global variable leaks (@aheckmann) +- [ab91204](https://github.com/mikeal/request/commit/ab9120495a89536c7152e3cdf17d684323b40474) Test that chunked responses are properly toString'ed (@isaacs) +- [9bff39f](https://github.com/mikeal/request/commit/9bff39fa485f28d7f1754e72f026418ca1186783) Properly flatten chunked responses (@isaacs) +- [8e4e956](https://github.com/mikeal/request/commit/8e4e95654391c71c22933ffd422fdc82d20ac059) Fix #52 Make the tests runnable with npm (@isaacs) +- [a9aa9d6](https://github.com/mikeal/request/commit/a9aa9d6d50ef0481553da3e50e40e723a58de10a) Fix #71 Respect the strictSSL flag (@isaacs) +- [#69](https://github.com/mikeal/request/pull/69) Flatten chunked requests properly (@isaacs) +- [#73](https://github.com/mikeal/request/pull/73) Fix #71 Respect the strictSSL flag (@isaacs) +- [#70](https://github.com/mikeal/request/pull/70) add test script to package.json (@isaacs) +- [08ca561](https://github.com/mikeal/request/commit/08ca5617e0d8bcadee98f10f94a49cbf2dd02862) Fixing case where encoding is set. Also cleaning up trailing whitespace because my editor likes to do that now. (@mikeal) +- [0be269f](https://github.com/mikeal/request/commit/0be269f7d9da6c3a14a59d5579546fee9d038960) Fixing case where no body exists. (@mikeal) +- [2f37bbc](https://github.com/mikeal/request/commit/2f37bbc51ff84c3c28ae419138a19bd33a9f0103) Fixing timeout tests. (@mikeal) +- [f551a2f](https://github.com/mikeal/request/commit/f551a2f02a87994249c2fd37dc8f20a29e8bf529) Fixing legacy naming of self as options. (@mikeal) +- [717789e](https://github.com/mikeal/request/commit/717789ec9f690e9d5216ce1c27688eef822940cc) Avoid duplicate emit when using a timeout (@Marsup) +- [#76](https://github.com/mikeal/request/pull/76) Bug when a request fails and a timeout is set (@Marsup) +- [c1d255e](https://github.com/mikeal/request/commit/c1d255e5bcc5791ab69809913fe6d917ab93c8b7) global leakage in request.defaults (@isaacs) +- [14070f2](https://github.com/mikeal/request/commit/14070f269c79cae6ef9e7f7a415867150599bb8e) Don't require SSL for non-SSL requests (@isaacs) +- [4b8f696](https://github.com/mikeal/request/commit/4b8f6965e14c6fb704cf16f5bc011e4787cf32b2) Set proxy auth instead of just setting auth a second time (@isaacs) +- [cd22fbd](https://github.com/mikeal/request/commit/cd22fbdb00b90c5c75187ecf41373cfbb4af5bcd) Merge branch 'proxy-auth-bug' (@isaacs) +- [#78](https://github.com/mikeal/request/pull/78) Don't try to do strictSSL for non-ssl connections (@isaacs) +- [d8c53fc](https://github.com/mikeal/request/commit/d8c53fceca3af385753880395c680f6ec3d4d560) Removing legacy call to sys.puts (@mikeal) +- [731b32b](https://github.com/mikeal/request/commit/731b32b654bb217de3466b8d149ce480988bb24b) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [9c897df](https://github.com/mikeal/request/commit/9c897dffc7e238f10eb7e14c61978d6821c70f56) Enhance redirect handling: (1) response._redirectsFollowed reports the total number of redirects followed instead of being reset to 0; (2) add response.redirects, an array of the response.statusCode and response.headers.location for each redirect. (@danmactough) +- [#81](https://github.com/mikeal/request/pull/81) Enhance redirect handling (@danmactough) +- [4c84001](https://github.com/mikeal/request/commit/4c8400103ec18a0729e29e9ffb17dda65ce02f6d) Document strictSSL option (@isaacs) +- [d517ac0](https://github.com/mikeal/request/commit/d517ac03278b3ebd9a46ca9f263bea68d655822b) allow passing in buffers as multipart bodies (@kkaefer) +- [6563865](https://github.com/mikeal/request/commit/6563865b80573ad3c68834a6633aff6d322b59d5) bugs[web] should be bugs[url] (@isaacs) +- [2625854](https://github.com/mikeal/request/commit/262585480c148c56772dfc8386cfc59d5d262ca0) add option followAllRedirects to follow post/put redirects +- [bc057af](https://github.com/mikeal/request/commit/bc057affb58272d9152766956e5cde4ea51ca043) fix typo, force redirects to always use GET +- [d68b434](https://github.com/mikeal/request/commit/d68b434693dbf848dff4c570c4249a35329cc24f) Support node 0.5.11-style url parsing (@isaacs) +- [#96](https://github.com/mikeal/request/pull/96) Authless parsed url host support (@isaacs) +- [9f66c6d](https://github.com/mikeal/request/commit/9f66c6d79bc6515d870b906df39bd9d6d9164994) Typo, causing 'TypeError: Cannot read property 'length' of undefined' (@isaacs) +- [#97](https://github.com/mikeal/request/pull/97) Typo in previous pull causes TypeError in non-0.5.11 versions (@isaacs) +- [b320e05](https://github.com/mikeal/request/commit/b320e05f2d84510f47a6b6857d091c8cd4d3ae2e) When no request body is being sent set 'content-length':0. fixes #89 (@mikeal) +- [059916c](https://github.com/mikeal/request/commit/059916c545a0faa953cb8ac66b8c3ae243b1c8ce) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [248e9d6](https://github.com/mikeal/request/commit/248e9d65e73ac868948a82d07feaf33387723a1d) Fix for pipe() after response. Added response event, fixed and updated tests, removed deprecated stream objects. (@mikeal) +- [a2e5d6e](https://github.com/mikeal/request/commit/a2e5d6e30d3e101f8c5a034ef0401fdde8608ccf) Fixing double callback firing. node 0.5 is much better about calling errors on the client object which, when aborting on timeout, predictable emits an error which then triggers a double callback. (@mikeal) +- [5f80577](https://github.com/mikeal/request/commit/5f805775e6aeaaf229cc781439b29108fb69f373) Release for 0.6 (@mikeal) +- [bf906de](https://github.com/mikeal/request/commit/bf906de601121b52c433b0af208550f1db892cde) Adding some oauth support, tested with Twitter. (@mikeal) +- [8869b2e](https://github.com/mikeal/request/commit/8869b2e88cc305e224556c5ca75b7b59311911d9) Removing irrelevant comments. (@mikeal) +- [8323eed](https://github.com/mikeal/request/commit/8323eed4915bb73b33544bc276f3840c13969134) Closed issue 82 : handling cookies - added tests too +- [739f841](https://github.com/mikeal/request/commit/739f84166d619778ab96fd0b0f4f1f43e8b0fdda) Closed issue 82 : handling cookies - added tests too +- [7daf841](https://github.com/mikeal/request/commit/7daf8415fb1a4e707ec54eb413169e49d8bbe521) Closed issue 82 : handling cookies - added tests too +- [6c22041](https://github.com/mikeal/request/commit/6c22041a4719bf081c827dda8f35e7b79b4c39d9) changed README +- [3db7f7d](https://github.com/mikeal/request/commit/3db7f7d38e95406b84f06fed52b69038b0250904) Updated README +- [6181b7a](https://github.com/mikeal/request/commit/6181b7a8a4be75bcf75cd3ff6dacb8e910737e92) Documented request.cookie() and request.jar() +- [fc44260](https://github.com/mikeal/request/commit/fc44260d13f0094bfe96d18878a11c6fe88b69e5) Tiny cookie example error on README +- [366831b](https://github.com/mikeal/request/commit/366831b705b5d5ebfbec5f63b4b140cbafcb4515) Remove instanceof check for CookieJar (mikeal suggestion) +- [88488cf](https://github.com/mikeal/request/commit/88488cf076efbd916b0326e0981e280c993963a7) Also add cookie to the user defined cookie jar (mikeal's suggestion) +- [f6fef5b](https://github.com/mikeal/request/commit/f6fef5bfa4ba8e1dfa3022df8991716e5cba7264) Updated cookie documentation in README file +- [b519044](https://github.com/mikeal/request/commit/b5190441a889164dfeb4148fac643fd7a87cfb51) request.defaults({jar: false}) disables cookies && also updated README +- [856a65c](https://github.com/mikeal/request/commit/856a65cd28402efbe3831a68d73937564a27ea9b) Update jar documentation in the options also +- [#102](https://github.com/mikeal/request/pull/102) Implemented cookies - closes issue 82: https://github.com/mikeal/request/issues/82 (@alessioalex) +- [62592e7](https://github.com/mikeal/request/commit/62592e7fe9ee5ecaee80b8f5bc2400e4a277e694) Cookie bugs (@janjongboom) +- [a06ad2f](https://github.com/mikeal/request/commit/a06ad2f955270974409e75c088e1f5d1f5298ff5) Follow redirects should work on PUT and POST requests as well. This is more consistent to other frameworks, e.g. .NET (@janjongboom) +- [bf3f5d3](https://github.com/mikeal/request/commit/bf3f5d30fdabf6946096623fc3398bb66ed19a1f) Cookies shouldn't be discarded when followRedirect = true (@janjongboom) +- [16db85c](https://github.com/mikeal/request/commit/16db85c07e6c2516269299640fdddca6db7bc051) Revert "Follow redirects should work on PUT and POST requests as well. This is more consistent to other frameworks, e.g. .NET" (@janjongboom) +- [841664e](https://github.com/mikeal/request/commit/841664e309f329be98c1a011c634f5291af1eebc) Add test for proxy option (@dominictarr) +- [#105](https://github.com/mikeal/request/pull/105) added test for proxy option. (@dominictarr) +- [50d2d39](https://github.com/mikeal/request/commit/50d2d3934cd86d7142a4aab66017bb1ef82329cf) Fixing test, emitter matches on req.url so it needs the full url. (@mikeal) +- [668a291](https://github.com/mikeal/request/commit/668a291013380af305eba12b1d5c7a5376a74c76) Adding some documentation for OAuth signing support. (@mikeal) +- [04faa3b](https://github.com/mikeal/request/commit/04faa3bf2b1f4ec710414c6ec7231b24767b2f89) Minor improvements in example (@mikeal) +- [0fddc17](https://github.com/mikeal/request/commit/0fddc1798dcd9b213e3f8aec504c61cecf4d7997) Another small fix to the url in the docs. (@mikeal) +- [337649a](https://github.com/mikeal/request/commit/337649a08b4263c0d108cd4621475c8ff9cf8dd0) Add oauth to options. (@mikeal) +- [#86](https://github.com/mikeal/request/pull/86) Can't post binary to multipart requests (@developmentseed) +- [4e4d428](https://github.com/mikeal/request/commit/4e4d4285490be20abf89ff1fb54fb5088c01c00e) Update to Iris Couch URL (@jhs) +- [#110](https://github.com/mikeal/request/pull/110) Update to Iris Couch URL (@iriscouch) +- [d7af099](https://github.com/mikeal/request/commit/d7af0994b382466367f2cafc5376150e661eeb9d) Remove the global `i` as it's causing my test suites to fail with leak detection turned on. (@3rd-Eden) +- [#117](https://github.com/mikeal/request/pull/117) Remove the global `i` (@3rd-Eden) +- [b2a4ad1](https://github.com/mikeal/request/commit/b2a4ad1e7d7553230e932ea093d7f77f38147ef9) Force all cookie keys into lower case as suggested by LinusU (@jhurliman) +- [055a726](https://github.com/mikeal/request/commit/055a7268b40425643d23bd6a4f09c7268dbab680) Applying a modified version of pull request #106 as suggested by janjongboom (@jhurliman) +- [#121](https://github.com/mikeal/request/pull/121) Another patch for cookie handling regression (@jhurliman) +- [a353f4e](https://github.com/mikeal/request/commit/a353f4eeb312ea378d34b624f5c4df33eefa152c) Merge remote-tracking branch 'upstream/master' (@janjongboom) +- [#104](https://github.com/mikeal/request/pull/104) Cookie handling contains bugs (@janjongboom) +- [a3be5ad](https://github.com/mikeal/request/commit/a3be5ad5ea112422ed00da632530b93bcf54727c) Fix encoding of characters like ( (@mikeal) +- [dd2067b](https://github.com/mikeal/request/commit/dd2067bbbf77d1132c9ed480848645136b8a5521) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [ddc4e45](https://github.com/mikeal/request/commit/ddc4e453c3b9a0e11da4df156c5e15206abfc1ef) Pushed new version to npm (@mikeal) +- [feee5eb](https://github.com/mikeal/request/commit/feee5ebd2ca8c09db25b5cb13cd951f7c4322a49) Real fix for encoding issues in javascript and oauth. (@mikeal) +- [23896cd](https://github.com/mikeal/request/commit/23896cdc66d75ec176876167ff21da72b7ff181b) Pushed new version to npm. (@mikeal) +- [a471ed2](https://github.com/mikeal/request/commit/a471ed2ca8acdca1010a0fc20434c5c9956b0d0c) HTTP redirect tests (@jhs) +- [a4a9aa1](https://github.com/mikeal/request/commit/a4a9aa199ff958630791e131092ec332ada00a49) A self-signed certificate for upcoming HTTPS testing (@jhs) +- [10ac6b9](https://github.com/mikeal/request/commit/10ac6b9db40263bec1bf63ee7e057000ffd2d7e9) HTTPS tests, for now a copy of the test-body tests (@jhs) +- [105aed1](https://github.com/mikeal/request/commit/105aed1ff99add1957f91df7efabf406e262f463) Support an "httpModules" object for custom http/https module behavior (@jhs) +- [#112](https://github.com/mikeal/request/pull/112) Support using a custom http-like module (@iriscouch) +- [d05a875](https://github.com/mikeal/request/commit/d05a8753af576fc1adccc7ffe9633690371c05ee) Test for #129 (@mikeal) +- [06cdfaa](https://github.com/mikeal/request/commit/06cdfaa3c29233dac3f47e156f2b5b3a0f0ae4b8) return body as buffer when encoding is null +- [#132](https://github.com/mikeal/request/pull/132) return the body as a Buffer when encoding is set to null (@jahewson) +- [4882e51](https://github.com/mikeal/request/commit/4882e519ed6b8d08795da5de37166148ce0ee440) fixed cookies parsing, updated tests (@afanasy) +- [2be228e](https://github.com/mikeal/request/commit/2be228ec8b48a60028bd1d80c8cbebf23964f913) Change `host` to `hostname` in request hash +- [#135](https://github.com/mikeal/request/pull/135) host vs hostname (@iangreenleaf) +- [e24abc5](https://github.com/mikeal/request/commit/e24abc5cc2c6fa154ae04fe58a16d135eeba4951) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [c99c809](https://github.com/mikeal/request/commit/c99c809bb48b9c0193aae3789c5c844f7f6cbe92) Reverting host -> hostname because it breaks in pre-0.6. (@mikeal) +- [a1134d8](https://github.com/mikeal/request/commit/a1134d855f928fde5c4fe9ee255c111da0195bfc) adding logging (@mikeal) +- [#133](https://github.com/mikeal/request/pull/133) Fixed cookies parsing (@afanasy) +- [9179471](https://github.com/mikeal/request/commit/9179471f9f63b6ba9c9078a35cb888337ce295e8) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [cbb180b](https://github.com/mikeal/request/commit/cbb180b0399074995c235a555e3e3e162d738f7c) Fixes to oauth test. (@mikeal) +- [e1c351f](https://github.com/mikeal/request/commit/e1c351f92958634ccf3fbe78aa2f5b06d9c9a5fa) Published new version. (@mikeal) +- [3ceee86](https://github.com/mikeal/request/commit/3ceee86f1f3aad3a6877d6d3813e087549f3b485) Formatting fixes. (@mikeal) +- [18e1af5](https://github.com/mikeal/request/commit/18e1af5e38168dcb95c8ae29bb234f1ad9bbbdf9) Fixing log error. (@mikeal) +- [edc19b5](https://github.com/mikeal/request/commit/edc19b5249f655714efa0f8fa110cf663b742921) Pushed new version. (@mikeal) +- [f51c32b](https://github.com/mikeal/request/commit/f51c32bd6f4da0419ed8404b610c43ee3f21cf92) added "form" option to readme. (@petejkim) +- [#144](https://github.com/mikeal/request/pull/144) added "form" option to readme (@petejkim) +- [b58022e](https://github.com/mikeal/request/commit/b58022ecda782af93e35e5f9601013b90b09ca73) add "forever" method (@thejh) +- [79d4651](https://github.com/mikeal/request/commit/79d46510ddff2e2c12c69f7ae4072ec489e27b0e) remove logging (@thejh) +- [f87cbf6](https://github.com/mikeal/request/commit/f87cbf6ec6fc0fc2869c340114514c887b304a80) retry on ECONNRESET on reused socket (@thejh) +- [1a91675](https://github.com/mikeal/request/commit/1a916757f4ec48b1282fddfa0aaa0fa6a1bf1267) Multipart requests should respect content-type if set; Issue #145 (@apeace) +- [#146](https://github.com/mikeal/request/pull/146) Multipart should respect content-type if previously set (@apeace) +- [#148](https://github.com/mikeal/request/pull/148) Retry Agent (@thejh) +- [70c5b63](https://github.com/mikeal/request/commit/70c5b63aca29a7d1629fa2909ff5b7199bbf0fd1) Publishing new version to npm. (@mikeal) +- [fc0f04b](https://github.com/mikeal/request/commit/fc0f04bab5d6be56a2c19d47d3e8386bd9a0b29e) Fix: timeout on socket, timeout after redirect +- [ef79e59](https://github.com/mikeal/request/commit/ef79e59bbb88ed3e7d4368fe3ca5eee411bda345) Fix: timeout after redirect 2 +- [c32a218](https://github.com/mikeal/request/commit/c32a218da2296e89a269f1832d95b12c4aa10852) merge master (@jroes) +- [d2d9b54](https://github.com/mikeal/request/commit/d2d9b545e5679b829d33deeba0b22f9050fd78b1) add line to docs describing followAllRedirects option (@jroes) +- [#90](https://github.com/mikeal/request/pull/90) add option followAllRedirects to follow post/put redirects (@jroes) +- [c08ab7e](https://github.com/mikeal/request/commit/c08ab7efaefd39c04deb6986716efe5a6069528e) Emit an event after we create the request object so that people can manipulate it before nextTick(). (@mikeal) +- [#162](https://github.com/mikeal/request/pull/162) Fix issue #159 (@dpetukhov) +- [e77a169](https://github.com/mikeal/request/commit/e77a1695c5c632c067857e99274f28a1d74301fe) fixing streaming example. fixes #164 (@mikeal) +- [ee53386](https://github.com/mikeal/request/commit/ee53386d85975c79b801edbb4f5bb7ff4c5dc90b) fixes #127 (@mikeal) +- [e2cd9de](https://github.com/mikeal/request/commit/e2cd9de9a9d10e1aa4cf4e26006bb30fa5086f0b) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [a0ab977](https://github.com/mikeal/request/commit/a0ab9770a8fb89f970bb3783ed4e6dde9e33511b) Added failing test for #125. (@papandreou) +- [c80800a](https://github.com/mikeal/request/commit/c80800a834b0f8bc0fb40d1fad4d4165a83369fd) Fix cookie jar/headers.cookie collision. Closes #125. (@papandreou) +- [1ac9e2d](https://github.com/mikeal/request/commit/1ac9e2d1bf776728a1fe676dd3693ef66f50f7f7) Redirect test: Also assert that the request cookie doesn't get doubled in the request for the landing page. (@papandreou) +- [07bbf33](https://github.com/mikeal/request/commit/07bbf331e2a0d40d261487f6222e8cafee0e50e3) Fixes #150 (@mikeal) +- [c640eed](https://github.com/mikeal/request/commit/c640eed292c06eac3ec89f60031ddf0fc0add732) Cookie jar handling: Don't double the cookies on each redirect (see discussion on #139). (@papandreou) +- [808de8b](https://github.com/mikeal/request/commit/808de8b0ba49d4bb81590ec37a873e6be4d9a416) Adding some missing mime types #138 (@serby) +- [#161](https://github.com/mikeal/request/pull/161) Fix cookie jar/headers.cookie collision (#125) (@papandreou) +- [#168](https://github.com/mikeal/request/pull/168) Picking off an EasyFix by adding some missing mimetypes. (@serby) +- [2a30487](https://github.com/mikeal/request/commit/2a304879f4218c1e46195d882bc81c0f874be329) bugfix - allow add cookie to wrapped request (defaults) (@fabianonunes) +- [a18b4f1](https://github.com/mikeal/request/commit/a18b4f14559f56cf52ca1b421daa6a934d28d51b) Making pipeDest a public prototype method rather than keeping it private. (@mikeal) +- [#170](https://github.com/mikeal/request/pull/170) can't create a cookie in a wrapped request (defaults) (@fabianonunes) +- [49a0f60](https://github.com/mikeal/request/commit/49a0f604779c91dd1759a02cbb195ccbd8d73f5d) Structural refactor, getting read for composable API. (@mikeal) +- [5daa0b2](https://github.com/mikeal/request/commit/5daa0b28b06cf109614f19e76b0e0b9b25ee3baf) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [e4df85c](https://github.com/mikeal/request/commit/e4df85c72221bf09ee7e1eb54f6c881851bd4164) Composable API for OAuth. (@mikeal) +- [945ec40](https://github.com/mikeal/request/commit/945ec40baef968ddd468c3b4dfce01621e4a0e31) Composable form API (@mikeal) +- [c30b47f](https://github.com/mikeal/request/commit/c30b47f229522a75af85da269157377b4a7dc37d) Use this, return this. (@mikeal) +- [e908644](https://github.com/mikeal/request/commit/e908644a69f9107b954f13635736f1e640216aec) Composable multipart API. (@mikeal) +- [e115677](https://github.com/mikeal/request/commit/e115677b1a03576eb96386986c350f211a4f38cd) Composable jar. Guard against overwrites on retry. (@mikeal) +- [a482e48](https://github.com/mikeal/request/commit/a482e4802e11fd122b12e18d1b18b49850fef823) Updating copyright for the new year. (@mikeal) +- [3c6581a](https://github.com/mikeal/request/commit/3c6581a9d4508fe5d75e111ae0fb94c5e0078404) Adding clobber argument for appending to headers. thanks @isaacs (@mikeal) +- [54e6aca](https://github.com/mikeal/request/commit/54e6aca0ab5982621fc9b35500f2154e50c0c95d) Fixes #144. (@mikeal) +- [12f4997](https://github.com/mikeal/request/commit/12f4997ed83bfbfefa3fc5b5635bc9a6829aa0d7) Fixing clobber. (@mikeal) +- [2f34fd1](https://github.com/mikeal/request/commit/2f34fd13b7ec86cb1c67e0a58664b9e060a34a50) Added support for a "query" option value that is a hash of querystring values that is merged (taking precedence over) with the querystring passed in the uri string. (@csainty) +- [a32d9e7](https://github.com/mikeal/request/commit/a32d9e7069533fb727a71730dbaa0f62ebefb731) Added a js based test runner so I can run tests on windows. (@csainty) +- [e0b6ce0](https://github.com/mikeal/request/commit/e0b6ce063de0c4223c97982128bb8203caf4a331) Tidied up an issue where ?> was being appended to URLs. (@csainty) +- [d47150d](https://github.com/mikeal/request/commit/d47150d6748a452df336d8de9743218028a876db) Refactored to match the composable style (@csainty) +- [b7e0929](https://github.com/mikeal/request/commit/b7e0929837873a8132476bb2b4d2e2a0fdc7cd0f) implemented issue #173 allow uri to be first argument (@twilson63) +- [b7264a6](https://github.com/mikeal/request/commit/b7264a6626481d5da50a28c91ea0be7b688c9daf) removed debug line and reset ports (@twilson63) +- [76598c9](https://github.com/mikeal/request/commit/76598c92bee64376e5d431285ac1bf6783140dbb) removed npm-debug (@twilson63) +- [#177](https://github.com/mikeal/request/pull/177) Issue #173 Support uri as first and optional config as second argument (@twilson63) +- [0f24051](https://github.com/mikeal/request/commit/0f240517dea65337636a49cb1cc2b5327504430e) Renamed query to qs. It was actually my first choice, but there appeared to be conflicts with the qs = require('querystring'). These are no longer present though and must have been unrelated. (@csainty) +- [becedaa](https://github.com/mikeal/request/commit/becedaaa7681b0c4ad5c0a9b9922fc950f091af2) Changed test structure to no longer require a server, modeled on the oauth tests. This also lets me revert some of the changes I had to make to the test server and proxy tests (@csainty) +- [9b2bbf0](https://github.com/mikeal/request/commit/9b2bbf0c12e87a59320efac67759041cd4af913f) Modified how the qs function works, it now no longer tweaks the existing request uri, instead it recreates a new one. This allows me to revert all the other changes I had to make previously and gives a nice clean commit that is self contained. (@csainty) +- [5ac7e26](https://github.com/mikeal/request/commit/5ac7e26ce4f7bf5a334df91df83699891171c0ae) failing test for .pipe(dst, opts) (@substack) +- [3b2422e](https://github.com/mikeal/request/commit/3b2422e62fbd6359b841e59a2c1888db71a22c2c) fix for failing pipe opts test (@substack) +- [8788c8b](https://github.com/mikeal/request/commit/8788c8b8cba96662e9d94a96eb04d96b904adea3) added uri param for post, put, head, del shortcuts (@twilson63) +- [#179](https://github.com/mikeal/request/pull/179) fix to add opts in .pipe(stream, opts) (@substack) +- [#180](https://github.com/mikeal/request/pull/180) Modified the post, put, head and del shortcuts to support uri optional param (@twilson63) +- [37d0699](https://github.com/mikeal/request/commit/37d0699eb681e85b7df4896b0a68b6865e596cb3) Fixing end bug i introduced being stupid. (@mikeal) +- [3a97292](https://github.com/mikeal/request/commit/3a97292f45273fa2cc937c0698ba19964780b4bb) fixed defaults functionality to support (uri, options, callback) (@twilson63) +- [#182](https://github.com/mikeal/request/pull/182) Fix request.defaults to support (uri, options, callback) api (@twilson63) +- [c94b200](https://github.com/mikeal/request/commit/c94b200258fa48697e386121a3e114ab7bed2ecf) Switched npm test from the bash script to a node script so that it is cross-platform. (@csainty) +- [#176](https://github.com/mikeal/request/pull/176) Querystring option (@csainty) +- [3b1e609](https://github.com/mikeal/request/commit/3b1e6094451e8d34c93353177de9d76e9a805e43) Adding defaults test back in. (@mikeal) +- [b4ae0c2](https://github.com/mikeal/request/commit/b4ae0c2d50f018a90a3ec8daa1d14c92a99873b9) Fixing idiotic bug I introduced. (@mikeal) +- [32f76c8](https://github.com/mikeal/request/commit/32f76c8baaf784dc2f4f1871153b1796bcebdcfe) Pushed new version to npm. (@mikeal) +- [00d0d9f](https://github.com/mikeal/request/commit/00d0d9f432182f13a5b8aa2e3a2a144b5c179015) Adding accept header to json support. (@mikeal) +- [0f580e6](https://github.com/mikeal/request/commit/0f580e6f6317c5301a52c0b6963d58e27112abca) Add abort support to the returned request (@itay) +- [4505e6d](https://github.com/mikeal/request/commit/4505e6d39a44229bfe5dc4d9a920233e05a7dfdb) Fixing some edge streaming cases with redirects by reusing the Request object. (@mikeal) +- [eed57af](https://github.com/mikeal/request/commit/eed57af8fe3e16632e9e0043d4d7f4d147dbfb8f) Published new version. (@mikeal) +- [97386b5](https://github.com/mikeal/request/commit/97386b5d7315b5c83702ffc7d0b09e34ecb67e04) Fixing pretty bad bug from the composable refactor. (@mikeal) +- [b693ce6](https://github.com/mikeal/request/commit/b693ce64e16aaa859d4edc86f82fbb11e00d33c0) Move abort to a prototype method, don't raise error (@itay) +- [1330eef](https://github.com/mikeal/request/commit/1330eef3ec84a651a435c95cf1ff1a4003086440) Merge branch 'master' of git://github.com/mikeal/request (@itay) +- [#188](https://github.com/mikeal/request/pull/188) Add abort support to the returned request (@itay) +- [5ff4645](https://github.com/mikeal/request/commit/5ff46453e713da1ae66a0d510eda4919e4080abe) Style changes. (@mikeal) +- [2dbd1e4](https://github.com/mikeal/request/commit/2dbd1e4350c2941b795b0e5ee7c0a00cd04cce09) Fixing new params style on master for head request. (@mikeal) +- [14989b2](https://github.com/mikeal/request/commit/14989b2dfc6830dbdad5364930fba1d2995aba06) Pushed new version to npm. (@mikeal) +- [0ea2351](https://github.com/mikeal/request/commit/0ea2351ef017ada9b8472f8d73086715ebe30c6a) Fixes #190. outdated check on options.json from before we had boolean support. (@mikeal) +- [21bf78c](https://github.com/mikeal/request/commit/21bf78c264316f75f4e6c571461521cda6ccf088) Adds a block on DELETE requests in status 300-400 (@goatslacker) +- [0c0c201](https://github.com/mikeal/request/commit/0c0c20139b28b21a860f72b8ce0124046fae421d) Adds tests for GH-119 Fix (@goatslacker) +- [#193](https://github.com/mikeal/request/pull/193) Fixes GH-119 (@goatslacker) +- [5815a69](https://github.com/mikeal/request/commit/5815a697347f20658dc2bdfd0d06e41d0aa0dac4) Fixes #194. setTimeout only works on node 0.6+ (@mikeal) +- [1ddcd60](https://github.com/mikeal/request/commit/1ddcd605bc8936c5b3534e1cf9aa1b29fa2b060b) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [7b35b4f](https://github.com/mikeal/request/commit/7b35b4ff63bbdf133f0f600a88a87b5723d29bdf) Removing old checks for self.req, it's ensured if start() is called. Implementing early pause/resume for when streams try to pause/resume before any data is emitted. Fixes #195. (@mikeal) +- [f01b79b](https://github.com/mikeal/request/commit/f01b79bb651f64065bac8877739223527f5b5592) Make ForeverAgent work with HTTPS (@isaacs) +- [#197](https://github.com/mikeal/request/pull/197) Make ForeverAgent work with HTTPS (@isaacs) +- [8d85b57](https://github.com/mikeal/request/commit/8d85b57ebb81c9d2d0a6b94aed41bf2ab0e3ad09) Forever inherits bugfix (@isaacs) +- [#198](https://github.com/mikeal/request/pull/198) Bugfix on forever usage of util.inherits (@isaacs) +- [37446f5](https://github.com/mikeal/request/commit/37446f54bb21cf9c83ffa81d354d799ae7ecf9ed) Add a test of HTTPS strict with CA checking (@isaacs) +- [8378d2e](https://github.com/mikeal/request/commit/8378d2ef9b8121a9851d21b3f6ec8304bde61c9d) Support tunneling HTTPS requests over proxies (@isaacs) +- [#199](https://github.com/mikeal/request/pull/199) Tunnel (@isaacs) +- [f0052ac](https://github.com/mikeal/request/commit/f0052ac5e6ca9f3f4aa49f6cda6ba15eb5d8b8e6) Published new version to npm. (@mikeal) +- [cea668f](https://github.com/mikeal/request/commit/cea668f6f7d444831313ccc0e0d301d25f2bd421) Adding more explicit error when undefined is passed as uri or options. (@mikeal) +- [047b7b5](https://github.com/mikeal/request/commit/047b7b52f3b11f4c44a02aeb1c3583940ddb59c7) Fix special method functions that get passed an options object. (@mikeal) +- [746de0e](https://github.com/mikeal/request/commit/746de0ef2f564534b29eeb8f296a59bd2c3086a7) pass through Basic authorization option for HTTPS tunneling +- [6fda9d7](https://github.com/mikeal/request/commit/6fda9d7d75e24cc1302995e41e26a91e03fdfc9a) Always clobber internal objects for qs but preserve old querystring args when clobber is present. (@mikeal) +- [75ca7a2](https://github.com/mikeal/request/commit/75ca7a25bc9c6102e87f3660a25835c7fcd70edb) Merge branch 'master' of https://github.com/mikeal/request +- [3b9f0fd](https://github.com/mikeal/request/commit/3b9f0fd3da4ae74de9ec76e7c66c57a7f8641df2) Fix cookies so that attributes are case insensitive +- [fddbd6e](https://github.com/mikeal/request/commit/fddbd6ee7d531bc4a82f629633b9d1637cb039e8) Properly set cookies during redirects +- [0d0bdb7](https://github.com/mikeal/request/commit/0d0bdb793f908492d4086fae8744f1e33e68d8c6) Remove request body when following non-GET redirects +- [#203](https://github.com/mikeal/request/pull/203) Fix cookie and redirect bugs and add auth support for HTTPS tunnel (@milewise) +- [b5fa773](https://github.com/mikeal/request/commit/b5fa773994de1799cf53491db7f5f3ba32825b20) Replace all occurrences of special chars in RFC3986 (@chriso) +- [bc6cd6c](https://github.com/mikeal/request/commit/bc6cd6ca6c6157bad76f0b2b23d4993f389ba977) documenting additional behavior of json option (@jphaas) +- [80e4e43](https://github.com/mikeal/request/commit/80e4e43186de1e9dcfaa1c9a921451560b91267c) Fixes #215. (@mikeal) +- [51f343b](https://github.com/mikeal/request/commit/51f343b9adfc11ec1b2ddcfb52a57e1e13feacb2) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [89c0f1d](https://github.com/mikeal/request/commit/89c0f1dd324bc65ad9c07436fb2c8220de388c42) titlecase authorization for oauth (@visnup) +- [#217](https://github.com/mikeal/request/pull/217) need to use Authorization (titlecase) header with Tumblr OAuth (@visnup) +- [8c163eb](https://github.com/mikeal/request/commit/8c163eb9349459839fc720658979d5c97a955825) Double quotes are optional, and the space after the ; could be required (@janjongboom) +- [#224](https://github.com/mikeal/request/pull/224) Multipart content-type change (@janjongboom) +- [96f4b9b](https://github.com/mikeal/request/commit/96f4b9b1f7b937a92f3f94f10d6d02f8878b6107) Style changes. (@mikeal) +- [b131c64](https://github.com/mikeal/request/commit/b131c64816f621cf15f8c51e76eb105778b4aad8) Adding safe .toJSON method. fixes #167 (@mikeal) +- [05d6e02](https://github.com/mikeal/request/commit/05d6e02c31ec4e6fcfadbfbe5414e701710f6e55) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [74ca9a4](https://github.com/mikeal/request/commit/74ca9a4852b666d30dd71421e8cc8b8a83177148) Unified error and complete handling. Fixes #171 (@mikeal) +- [a86c7dc](https://github.com/mikeal/request/commit/a86c7dc7d0a7c640c7def4c0215e46e76a11ff56) Fixing followAllRedirects and all the redirect tests. (@mikeal) +- [#211](https://github.com/mikeal/request/pull/211) Replace all occurrences of special chars in RFC3986 (@chriso) +- [7e24e8a](https://github.com/mikeal/request/commit/7e24e8a48d0dcfe10d0cc08b3c4e9627b9a95a97) New version on npm, first 3.0 release candidate. (@mikeal) +- [22e0f0d](https://github.com/mikeal/request/commit/22e0f0d73459c11b81b0f66a2cde85492dd8e38f) Added test for .toJSON() (@mikeal) +- [df32746](https://github.com/mikeal/request/commit/df32746f157948b6ae05e87a35cf1768e065ef0b) Adding toJSON to npm test. (@mikeal) +- [e65bfba](https://github.com/mikeal/request/commit/e65bfba98f0886a059a268dcdceabf41aec1e5cc) New version in npm. (@mikeal) +- [2b95921](https://github.com/mikeal/request/commit/2b959217151aaff7a6e7cc15e2acfccd1bbb9b85) Fixing defaults when url is passed instead of uri. (@mikeal) +- [e0534d8](https://github.com/mikeal/request/commit/e0534d860b4931a7a6e645b328fd4418a5433057) Pushed new version to npm. (@mikeal) +- [d2dc835](https://github.com/mikeal/request/commit/d2dc83538379e9e1fafb94f5698c56b4a5318d8d) don't error when null is passed for options (@polotek) +- [db80bf0](https://github.com/mikeal/request/commit/db80bf0444bd98c45f635f305154b9da20eed328) expose initParams (@polotek) +- [8cf019c](https://github.com/mikeal/request/commit/8cf019c9f9f719694408840823e92da08ab9dac3) allow request.defaults to override the main request method (@polotek) +- [#240](https://github.com/mikeal/request/pull/240) don't error when null is passed for options (@polotek) +- [69d017d](https://github.com/mikeal/request/commit/69d017de57622429f123235cc5855f36b3e18d1c) added dynamic boundary for multipart requests (@zephrax) +- [fc13e18](https://github.com/mikeal/request/commit/fc13e185f5e28a280d347e61622ba708e1cd7bbc) added dynamic boundary for multipart requests (@zephrax) +- [#243](https://github.com/mikeal/request/pull/243) Dynamic boundary (@zephrax) +- [1764176](https://github.com/mikeal/request/commit/176417698a84c53c0a69bdfd2a05a2942919816c) Fixing the set-cookie header (@jeromegn) +- [#246](https://github.com/mikeal/request/pull/246) Fixing the set-cookie header (@jeromegn) +- [6f9da89](https://github.com/mikeal/request/commit/6f9da89348b848479c23192c04b3c0ddd5a4c8bc) do not set content-length header to 0 when self.method is GET or self.method is undefined (@sethbridges) +- [efc0ea4](https://github.com/mikeal/request/commit/efc0ea44d63372a30011822ad9d37bd3d7b85952) Experimental AWS signing. Signing code from knox. (@mikeal) +- [4c08a1c](https://github.com/mikeal/request/commit/4c08a1c10bc0ebb679e212ad87419f6c4cc341eb) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [fdb10eb](https://github.com/mikeal/request/commit/fdb10eb493110b8e6e4f679524f38cef946e3f08) Adding support for aws in options. (@mikeal) +- [dac6a30](https://github.com/mikeal/request/commit/dac6a301ae03207af88fae6f5017e82157b79b41) Fixing upgraded stat size and supporting content-type and content-md5 properly. (@mikeal) +- [98cb503](https://github.com/mikeal/request/commit/98cb50325e1d7789fd9f44523d2315df5f890d10) Allow body === '' /* the empty string */. (@Filirom1) +- [0e9ac12](https://github.com/mikeal/request/commit/0e9ac12c69aaca370fbca94b41358e1c3a2f6170) fixed just another global leak of i (@sreuter) +- [#260](https://github.com/mikeal/request/pull/260) fixed just another leak of 'i' (@sreuter) +- [#255](https://github.com/mikeal/request/pull/255) multipart allow body === '' ( the empty string ) (@Filirom1) +- [#249](https://github.com/mikeal/request/pull/249) Fix for the fix of your (closed) issue #89 where self.headers[content-length] is set to 0 for all methods (@sethbridges) +- [adc9ab1](https://github.com/mikeal/request/commit/adc9ab1f563f3cb4681ac8241fcc75e6099efde2) style changes. making @rwaldron cry (@mikeal) +- [155e6ee](https://github.com/mikeal/request/commit/155e6ee270924d5698d3fea37cefc1926cbaf998) Fixed `pool: false` to not use the global agent (@timshadel) +- [1232a8e](https://github.com/mikeal/request/commit/1232a8e46752619d4d4b51d558e6725faf7bf3aa) JSON test should check for equality (@timshadel) +- [#261](https://github.com/mikeal/request/pull/261) Setting 'pool' to 'false' does NOT disable Agent pooling (@timshadel) +- [#262](https://github.com/mikeal/request/pull/262) JSON test should check for equality (@timshadel) +- [914a723](https://github.com/mikeal/request/commit/914a72300702a78a08263fe98a43d25e25713a70) consumer_key and token_secret need to be encoded for OAuth v1 (@nanodocumet) +- [500e790](https://github.com/mikeal/request/commit/500e790f8773f245ff43dd9c14ec3d5c92fe0b9e) Fix uncontrolled crash when "this.uri" is an invalid URI (@naholyr) +- [#265](https://github.com/mikeal/request/pull/265) uncaughtException when redirected to invalid URI (@naholyr) +- [#263](https://github.com/mikeal/request/pull/263) Bug in OAuth key generation for sha1 (@nanodocumet) +- [f4b87cf](https://github.com/mikeal/request/commit/f4b87cf439453b3ca1d63e85b3aeb3373ee1f17e) I'm not OCD seriously (@TehShrike) +- [#268](https://github.com/mikeal/request/pull/268) I'm not OCD seriously (@TehShrike) +- [fcab7f1](https://github.com/mikeal/request/commit/fcab7f1953cd6fb141a7d98f60580c50b59fb73f) Adding a line break to the preamble as the first part of a multipart was not recognized by a server I was communicating with. (@proksoup) +- [661b62e](https://github.com/mikeal/request/commit/661b62e5319bf0143312404f1fc81c895c46f6e6) Commenting out failing post test. Need to figure out a way to test this now that the default is to use a UUID for the frontier. (@mikeal) +- [7165c86](https://github.com/mikeal/request/commit/7165c867fa5dea4dcb0aab74d2bf8ab5541e3f1b) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [5a7ca9b](https://github.com/mikeal/request/commit/5a7ca9b398c1300c08a28fb7f266054c3ce8c57a) Added drain event and returning the boolean from write to proper handle back pressure when piping. (@mafintosh) +- [#273](https://github.com/mikeal/request/pull/273) Pipe back pressure issue (@mafintosh) +- [f8ae8d1](https://github.com/mikeal/request/commit/f8ae8d18627e4743996d8600f77f4e4c05a2a590) New version in npm. (@mikeal) +- [7ff5dae](https://github.com/mikeal/request/commit/7ff5daef152bcfac5b02e661e5476a57b9693489) Merge remote-tracking branch 'upstream/master' (@proksoup) +- [1f34700](https://github.com/mikeal/request/commit/1f34700e5614ea2a2d78b80dd467c002c3e91cb3) fix tests with boundary by injecting boundry from header (@benatkin) +- [ee2b2c2](https://github.com/mikeal/request/commit/ee2b2c2f7a8625fde4d71d79e19cdc5d98f09955) Like in [node.js](https://github.com/joyent/node/blob/master/lib/net.js#L52) print logs if NODE_DEBUG contains the word request (@Filirom1) +- [#279](https://github.com/mikeal/request/pull/279) fix tests with boundary by injecting boundry from header (@benatkin) +- [3daebaf](https://github.com/mikeal/request/commit/3daebaf2551c8d0df7dac1ebff0af4fe08608768) Merge branch 'master' of https://github.com/mikeal/request (@proksoup) +- [dba2ebf](https://github.com/mikeal/request/commit/dba2ebf09552258f37b60122c19b236064b0d216) Updating with corresponding tests. (@proksoup) +- [396531d](https://github.com/mikeal/request/commit/396531d083c94bc807a25f7c3a50a0c92a00c5f7) Removing console.log of multipart (@proksoup) +- [54226a3](https://github.com/mikeal/request/commit/54226a38816b4169e0a7a5d8b1a7feba78235fec) Okay, trying it as an optional parameter, with a new test in test-body.js to verify (@proksoup) +- [23ae7d5](https://github.com/mikeal/request/commit/23ae7d576cc63d645eecf057112b71d6cb73e7b1) Remove non-"oauth_" parameters from being added into the OAuth Authorization header (@jplock) +- [8b82ef4](https://github.com/mikeal/request/commit/8b82ef4ff0b50b0c8dcfb830f62466fa30662666) Removing guard, there are some cases where this is valid. (@mikeal) +- [82440f7](https://github.com/mikeal/request/commit/82440f76f22a5fca856735af66e2dc3fcf240c0d) Adding back in guard for _started, need to keep some measure of safety but we should defer this restriction for as long as possible. (@mikeal) +- [#282](https://github.com/mikeal/request/pull/282) OAuth Authorization header contains non-"oauth_" parameters (@jplock) +- [087be3e](https://github.com/mikeal/request/commit/087be3ebbada53699d14839374f1679f63f3138f) Remove stray `console.log()` call in multipart generator. (@bcherry) +- [0a8a5ab](https://github.com/mikeal/request/commit/0a8a5ab6a08eaeffd45ef4e028be2259d61bb0ee) Merge remote-tracking branch 'upstream/master' (@proksoup) +- [#241](https://github.com/mikeal/request/pull/241) Composability updates suggested by issue #239 (@polotek) +- [#284](https://github.com/mikeal/request/pull/284) Remove stray `console.log()` call in multipart generator. (@bcherry) +- [8344666](https://github.com/mikeal/request/commit/8344666f682a302c914cce7ae9cea8de054f9240) Fix #206 Change HTTP/HTTPS agent when redirecting between protocols (@isaacs) +- [#272](https://github.com/mikeal/request/pull/272) Boundary begins with CRLF? (@proksoup) +- [#214](https://github.com/mikeal/request/pull/214) documenting additional behavior of json option (@jphaas) +- [#207](https://github.com/mikeal/request/pull/207) Fix #206 Change HTTP/HTTPS agent when redirecting between protocols (@isaacs) +- [9cadd61](https://github.com/mikeal/request/commit/9cadd61d989e85715ea07da8770a3077db41cca3) Allow parser errors to bubble up to request (@mscdex) +- [6a00fea](https://github.com/mikeal/request/commit/6a00fea09eed99257c0aec2bb66fbf109b0f573a) Only add socket error handler callback once (@mscdex) +- [975ea90](https://github.com/mikeal/request/commit/975ea90bed9503c67055b20e36baf4bcba54a052) Fix style (@mscdex) +- [205dfd2](https://github.com/mikeal/request/commit/205dfd2e21c13407d89d3ed92dc2b44b987d962b) Use .once() when listening for parser error (@mscdex) +- [ff9b564](https://github.com/mikeal/request/commit/ff9b5643d6b5679a9e7d7997ec6275dac10b000e) Add a space after if (@Filirom1) +- [#280](https://github.com/mikeal/request/pull/280) Like in node.js print options if NODE_DEBUG contains the word request (@Filirom1) +- [d38e57b](https://github.com/mikeal/request/commit/d38e57bbb3d827aa87427f2130aa5a5a3a973161) Test for #289 (@isaacs) +- [820af58](https://github.com/mikeal/request/commit/820af5839f2a193d091d98f23fd588bd919e3e58) A test of POST redirect following with 303 status (@isaacs) +- [7adc5a2](https://github.com/mikeal/request/commit/7adc5a21869bc92cc3b5e84d32c585952c8e5e87) Use self.encoding when calling Buffer.toString() (@isaacs) +- [#290](https://github.com/mikeal/request/pull/290) A test for #289 (@isaacs) +- [#293](https://github.com/mikeal/request/pull/293) Allow parser errors to bubble up to request (@mscdex) +- [ed68b8d](https://github.com/mikeal/request/commit/ed68b8dd024561e9d47d80df255fb79d783c13a7) Updated the twitter oauth dance. The comments weren't clear. Also removed token_key. No longer needed with twitter oauth. (@joemccann) +- [6bc19cd](https://github.com/mikeal/request/commit/6bc19cda351b59f8e45405499a100abd0b456e42) Forgot to remove token_secret; no longer needed for twitter. (@joemccann) +- [1f21b17](https://github.com/mikeal/request/commit/1f21b17fc4ff3a7011b23e3c9261d66effa3aa40) Adding form-data support. (@mikeal) +- [827e950](https://github.com/mikeal/request/commit/827e950500746eb9d3a3fa6f174416b194c9dedf) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [b211200](https://github.com/mikeal/request/commit/b2112009a31fc7f9122970d392750f62b6e77111) Test fixes for relative import. Adding to run all (@mikeal) +- [1268195](https://github.com/mikeal/request/commit/1268195b75bd5bb3954b4c4f2d9feb80a97994d1) Bundling mime module rather than keep around our own mime-map. (@mikeal) +- [4f51cec](https://github.com/mikeal/request/commit/4f51cecdc363946b957585c3deccfd8c37e19aa0) Docs for the form API, pumping version. (@mikeal) +- [90245d7](https://github.com/mikeal/request/commit/90245d7199215d7b195cf7e36b203ca0bd0a6bd3) Doc fixes. (@mikeal) +- [d98ef41](https://github.com/mikeal/request/commit/d98ef411c560bd1168f242c524a378914ff8eac4) Pushed new version to npm. (@mikeal) +- [3e11937](https://github.com/mikeal/request/commit/3e119375acda2da225afdb1596f6346dbd551fba) Pass servername to tunneling secure socket creation (@isaacs) +- [7725b23](https://github.com/mikeal/request/commit/7725b235fdec8889c0c91d55c99992dc683e2e22) Declare dependencies more sanely (@isaacs) +- [#317](https://github.com/mikeal/request/pull/317) Workaround for #313 (@isaacs) +- [#318](https://github.com/mikeal/request/pull/318) Pass servername to tunneling secure socket creation (@isaacs) +- [0c470bc](https://github.com/mikeal/request/commit/0c470bccf1ec097ae600b6116e6244cb624dc00e) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [0d98e5b](https://github.com/mikeal/request/commit/0d98e5b7ea6bd9c4f21535d3682bbed2f2e05df4) Pushed new version to npm. (@mikeal) +- [64a4448](https://github.com/mikeal/request/commit/64a44488ac8c792a1f548f305fc5c61efe0d77fb) when setting defaults, the wrapper adds the jar method assuming it has the same signature as get, meaning undefined is passed into initParams, which subsequently fails. now passing jar function directly as it has no need of defaults anyway seeing as it only creates a new cookie jar (@StuartHarris) +- [48c9881](https://github.com/mikeal/request/commit/48c988118bda4691fffbfcf30d5a39b6c1438736) Added test to illustrate #321 (@alexindigo) +- [8ce0f2a](https://github.com/mikeal/request/commit/8ce0f2a3b6929cd0f7998e00d850eaf5401afdb7) Added *src* stream removal on redirect. #321 (@alexindigo) +- [c32f0bb](https://github.com/mikeal/request/commit/c32f0bb9feaa71917843856c23b4aae99f78ad4d) Do not try to remove listener from an undefined connection (@strk) +- [#326](https://github.com/mikeal/request/pull/326) Do not try to remove listener from an undefined connection (@CartoDB) +- [#322](https://github.com/mikeal/request/pull/322) Fix + test for piped into request bumped into redirect. #321 (@alexindigo) +- [85b6a63](https://github.com/mikeal/request/commit/85b6a632ac7d3456485fbf931043f10f5f6344a5) New version in npm. (@mikeal) +- [f462bd3](https://github.com/mikeal/request/commit/f462bd3fa421fa5e5ca6c91852333db90297b80e) Rolling trunk version. (@mikeal) +- [8a82c5b](https://github.com/mikeal/request/commit/8a82c5b0990cc58fa4cb7f81814d13ba7ae35453) Adding url to redirect error for better debugging. (@mikeal) +- [013c986](https://github.com/mikeal/request/commit/013c986d0a8b5b2811cd06dd3733f4a3d37df1cc) Better debugging of max redirect errors. (@mikeal) +- [#320](https://github.com/mikeal/request/pull/320) request.defaults() doesn't need to wrap jar() (@redbadger) +- [4797f88](https://github.com/mikeal/request/commit/4797f88b42c3cf8680cbde09bf473678a5707aed) Fix #296 - Only set Content-Type if body exists (@Marsup) +- [f6bcf3e](https://github.com/mikeal/request/commit/f6bcf3eb51982180e813c69cccb942734f815ffe) fixup aws function to work in more situations (@nlf) +- [ba6c88a](https://github.com/mikeal/request/commit/ba6c88af5e771c2a0e007e6166e037a149561e09) added short blurb on using aws (@nlf) +- [#343](https://github.com/mikeal/request/pull/343) Allow AWS to work in more situations, added a note in the README on its usage (@nathan-lafreniere) +- [288c52a](https://github.com/mikeal/request/commit/288c52a2a1579164500c26136552827112801ff1) switch to a case insensitive getter when fetching headers for aws auth signing (@nlf) +- [#332](https://github.com/mikeal/request/pull/332) Fix #296 - Only set Content-Type if body exists (@Marsup) +- [7a16286](https://github.com/mikeal/request/commit/7a162868de65b6de15e00c1f707b5e0f292c5f86) Emit errors for anything in init so that it is catchable in a redirect. (@mikeal) +- [d288d21](https://github.com/mikeal/request/commit/d288d21d709fa81067f5af53737dfde06f842262) fix bug (@azylman) +- [#355](https://github.com/mikeal/request/pull/355) stop sending erroneous headers on redirected requests (@azylman) +- [b0b97f5](https://github.com/mikeal/request/commit/b0b97f53a9e94f3aeaa05e2cda5b820668f6e3b2) delete _form along with everything else on a redirect (@jgautier) +- [#360](https://github.com/mikeal/request/pull/360) Delete self._form along with everything else on redirect (@jgautier) +- [61e3850](https://github.com/mikeal/request/commit/61e3850f0f91ca6732fbd06b46796fbcd2fea1ad) Made it so that if we pass in Content-Length or content-length in the headers, don't make a new version (@danjenkins) +- [#361](https://github.com/mikeal/request/pull/361) Don't create a Content-Length header if we already have it set (@danjenkins) +- [590452d](https://github.com/mikeal/request/commit/590452d6569e68e480d4f40b88022f1b81914ad6) inside oauth.hmacsign: running rfc3986 on base_uri instead of just encodeURIComponent. +- [#362](https://github.com/mikeal/request/pull/362) Running `rfc3986` on `base_uri` in `oauth.hmacsign` instead of just `encodeURIComponent` (@jeffmarshall) +- [f7dc90c](https://github.com/mikeal/request/commit/f7dc90c8dae743d5736dc6c807eecde613eb4fd4) Revert "Merge pull request #362 from jeffmarshall/master" (@mikeal) +- [d631a26](https://github.com/mikeal/request/commit/d631a26e263077eca3d4925de9b0a8d57365ba90) reintroducing the WTF escape + encoding, also fixing a typo. +- [#363](https://github.com/mikeal/request/pull/363) rfc3986 on base_uri, now passes tests (@jeffmarshall) +- [bfe2791](https://github.com/mikeal/request/commit/bfe2791f596b749eed6961159d41a404c3aba0d0) oauth fix. (@mikeal) +- [#344](https://github.com/mikeal/request/pull/344) Make AWS auth signing find headers correctly (@nathan-lafreniere) +- [e863f25](https://github.com/mikeal/request/commit/e863f25336abc7b9f9936c20e0c06da8db0c6593) style change. (@mikeal) +- [3e5a87c](https://github.com/mikeal/request/commit/3e5a87ce28b3bb45861b32f283cd20d0084d78a7) Don't remove x_auth_type for Twitter reverse auth (@drudge) +- [#369](https://github.com/mikeal/request/pull/369) Don't remove x_auth_mode for Twitter reverse auth (@drudge) +- [25d4667](https://github.com/mikeal/request/commit/25d466773c43949e2eea4236ffc62841757fd1f0) x_auth_mode not x_auth_type (@drudge) +- [#370](https://github.com/mikeal/request/pull/370) Twitter reverse auth uses x_auth_mode not x_auth_type (@drudge) +- [cadf4dc](https://github.com/mikeal/request/commit/cadf4dc54f4ee3fae821f6beb1ea6443e528bf6f) massive style commit. (@mikeal) +- [33453a5](https://github.com/mikeal/request/commit/33453a53bc37e4499853b9d929b3603cdf7a31cd) New version in npm. (@mikeal) +- [b638185](https://github.com/mikeal/request/commit/b6381854006470af1d0607f636992c7247b6720f) Setting master version. (@mikeal) +- [8014d2a](https://github.com/mikeal/request/commit/8014d2a5b797f07cf56d2f39a346031436e1b064) correct Host header for proxy tunnel CONNECT (@ypocat) +- [#374](https://github.com/mikeal/request/pull/374) Correct Host header for proxy tunnel CONNECT (@ypocat) +- [8c3e9cb](https://github.com/mikeal/request/commit/8c3e9cb529767cff5e7206e2e76531183085b42a) If one of the request parameters is called "timestamp", the "oauth_timestamp" OAuth parameter will get removed during the parameter cleanup loop. (@jplock) +- [#375](https://github.com/mikeal/request/pull/375) Fix for missing oauth_timestamp parameter (@jplock) +- [69e6dc5](https://github.com/mikeal/request/commit/69e6dc5c80e67bbd7d135c3ceb657a1b2df58763) Fixed headers piping on redirects (@kapetan) +- [#376](https://github.com/mikeal/request/pull/376) Headers lost on redirect (@kapetan) +- [62dbbf3](https://github.com/mikeal/request/commit/62dbbf3d77b0851ba424d4f09d1d0c0be91c1f2d) Resolving the Invalid signature when using "qs" (@landeiro) +- [d4cf4f9](https://github.com/mikeal/request/commit/d4cf4f98e11f9a85b6bdfd0481c85c8ac34061ce) fixes missing host header on retried request when using forever agent +- [#380](https://github.com/mikeal/request/pull/380) Fixes missing host header on retried request when using forever agent (@mac-) +- [#381](https://github.com/mikeal/request/pull/381) Resolving "Invalid signature. Expected signature base string: " (@landeiro) +- [ea2f975](https://github.com/mikeal/request/commit/ea2f975ae83efe956b77cbcd0fd9ad42c0d5192f) Ensure that uuid is treated as a property name, not an index. (@othiym23) +- [#388](https://github.com/mikeal/request/pull/388) Ensure "safe" toJSON doesn't break EventEmitters (@othiym23) +- [11a3bc0](https://github.com/mikeal/request/commit/11a3bc0ea3063f6f0071248e03c8595bfa9fd046) Add more reporting to tests (@mmalecki) +- [#398](https://github.com/mikeal/request/pull/398) Add more reporting to tests (@mmalecki) +- [b85bf63](https://github.com/mikeal/request/commit/b85bf633fe8197dc38855f10016a0a76a8ab600a) Optimize environment lookup to happen once only (@mmalecki) +- [#403](https://github.com/mikeal/request/pull/403) Optimize environment lookup to happen once only (@mmalecki) +- [dbb9a20](https://github.com/mikeal/request/commit/dbb9a205fafd7bf5a05d2dbe7eb2c6833b4387dc) renaming tests/googledoodle.png to match it's actual image type of jpeg (@nfriedly) +- [e2d7d4f](https://github.com/mikeal/request/commit/e2d7d4fd35869354ba14a333a4b4989b648e1971) Add more auth options, including digest support (@nylen) +- [d0d536c](https://github.com/mikeal/request/commit/d0d536c1e5a9a342694ffa5f14ef8fbe8dcfa8bd) Add tests for basic and digest auth (@nylen) +- [85fd359](https://github.com/mikeal/request/commit/85fd359890646ef9f55cc6e5c6a32e74f4fbb786) Document new auth options (@nylen) +- [#338](https://github.com/mikeal/request/pull/338) Add more auth options, including digest support (@nylen) +- [fd2e2fa](https://github.com/mikeal/request/commit/fd2e2fa1e6d580cbc34afd3ae1200682cecb3cf9) Fixed a typo. (@jerem) +- [#415](https://github.com/mikeal/request/pull/415) Fixed a typo. (@jerem) +- [53c1508](https://github.com/mikeal/request/commit/53c1508c9c6a58f7d846de82cad36402497a4a4f) Fix for #417 (@mikeal) +- [b23f985](https://github.com/mikeal/request/commit/b23f985e02da4a96f1369541a128c4204a355666) Fixing merge conflict. (@mikeal) +- [28e8be5](https://github.com/mikeal/request/commit/28e8be5175793ac99236df88e26c0139a143e32d) Lost a forever fix in the previous merge. Fixing. (@mikeal) +- [e4d1e25](https://github.com/mikeal/request/commit/e4d1e25c1648ef91f6baf1ef407c712509af4b66) Copy options before adding callback. (@nrn) +- [22bc67d](https://github.com/mikeal/request/commit/22bc67d7ac739e9c9f74c026f875a0a7c686e29d) Respect specified {Host,host} headers, not just {host} (@andrewschaaf) +- [#430](https://github.com/mikeal/request/pull/430) Respect specified {Host,host} headers, not just {host} (@andrewschaaf) +- [6b11acf](https://github.com/mikeal/request/commit/6b11acf3e29fb84daef4e940314cae5ac2e580c6) Updating form-data. (@mikeal) +- [d195845](https://github.com/mikeal/request/commit/d195845c3e1de42c9aee752eec8efa4dda87ec74) Updating mime (@mikeal) +- [20ba1d6](https://github.com/mikeal/request/commit/20ba1d6d38191aa7545b927a7262a18c5c63575b) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [0150d9f](https://github.com/mikeal/request/commit/0150d9fa13e51d99880013b9ec29343850b40c2f) Consider `options.rejectUnauthorized` when pooling https agents (@mmalecki) +- [3e07b6d](https://github.com/mikeal/request/commit/3e07b6d4b81037d0e6e595670db483708ffa8698) Use `rejectUnauthorized: false` in tests (@mmalecki) +- [3995878](https://github.com/mikeal/request/commit/3995878d9fff18a8707f27ffeb4ed6401086adce) Support `key` and `cert` options (@mmalecki) +- [#433](https://github.com/mikeal/request/pull/433) Added support for HTTPS cert & key (@indexzero) +- [8b0f4e8](https://github.com/mikeal/request/commit/8b0f4e8fba33d578a891218201d87e3316ea9844) Released 2.14.0 (@mikeal) +- [54172c6](https://github.com/mikeal/request/commit/54172c68cab8360372e1e64e3fa14902662950bd) Rolling master version. (@mikeal) +- [aa4a285](https://github.com/mikeal/request/commit/aa4a28586354901b0c9b298a0aa79abb5ed175af) Add patch convenience method. (@mloar) +- [66501b9](https://github.com/mikeal/request/commit/66501b9872abc9a2065430cd5ed4a34dd45c8bee) protect against double callback (@spollack) +- [#444](https://github.com/mikeal/request/pull/444) protect against double callbacks on error path (@spollack) +- [#448](https://github.com/mikeal/request/pull/448) Convenience method for PATCH (@mloar) +- [6f0f8c5](https://github.com/mikeal/request/commit/6f0f8c5ee2b2fdc7118804664c2215fe9cb5a2f2) No longer doing bundle dependencies (@mikeal) +- [3997f98](https://github.com/mikeal/request/commit/3997f980722241c18454a00aeeda07d701c27a8f) No longer using bundle dependencies (@mikeal) +- [cba36ce](https://github.com/mikeal/request/commit/cba36ce64e68bd26e230b65f81256776ac66e686) Adding hawk signing to request. (@mikeal) +- [c7a8be6](https://github.com/mikeal/request/commit/c7a8be6d174eff05a9cb2fda987979e475d8543f) Fixing bug in empty options. (@mikeal) +- [67d753f](https://github.com/mikeal/request/commit/67d753fec99fa1f5a3b35ec0bbbc98896418d86c) node-uuid is much better. (@mikeal) +- [337718b](https://github.com/mikeal/request/commit/337718baa08cafb3e706d275fd7344a3c92363bb) Smarter test runner. (@mikeal) +- [bcc33ac](https://github.com/mikeal/request/commit/bcc33aca57baf6fe2a81fbf5983048c9220c71b1) Moved the cookie jar in to it's own module. (@mikeal) +- [3261be4](https://github.com/mikeal/request/commit/3261be4b5d6f45f62b9f50bec18af770cbb70957) Put aws signing in its own package. (@mikeal) +- [fbed723](https://github.com/mikeal/request/commit/fbed7234d7b532813105efdc4c54777396a6773b) OAuth signing is now in its own library. (@mikeal) +- [ef5ab90](https://github.com/mikeal/request/commit/ef5ab90277fb00d0e8eb1c565b0f6ef8c52601d3) Forever agent is now it's own package. (@mikeal) +- [ca1ed81](https://github.com/mikeal/request/commit/ca1ed813c62c7493dc77108b3efc907cc36930cb) tunneling agent is now it's own library. (@mikeal) +- [5c75621](https://github.com/mikeal/request/commit/5c75621ba5cea18bcf114117112121d361e5f3c9) Moving from main.js to index. cause it's not 2010 anymore. (@mikeal) +- [#413](https://github.com/mikeal/request/pull/413) rename googledoodle.png to .jpg (@nfriedly) +- [b4c4c28](https://github.com/mikeal/request/commit/b4c4c28424d906cd96a2131010b21d7facf8b666) Merge branch 'master' of github.com:mikeal/request (@nrn) +- [#310](https://github.com/mikeal/request/pull/310) Twitter Oauth Stuff Out of Date; Now Updated (@joemccann) +- [8b0e7e8](https://github.com/mikeal/request/commit/8b0e7e8c9d196d7286d1563aa54affcc4c8b0e1d) Comment to explain init() and start(). (@mikeal) +- [43d578d](https://github.com/mikeal/request/commit/43d578dc0206388eeae9584f540d550a06308fc8) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [b7c5ed4](https://github.com/mikeal/request/commit/b7c5ed48b618f71f138f9f08f8d705336f907e01) destroy the response if present when destroying the request (@mafintosh) +- [b279277](https://github.com/mikeal/request/commit/b279277dc2fb4b649640322980315d74db0d13f3) response.abort should be response.destroy (@mafintosh) +- [#454](https://github.com/mikeal/request/pull/454) Destroy the response if present when destroying the request (clean merge) (@mafintosh) +- [#429](https://github.com/mikeal/request/pull/429) Copy options before adding callback. (@nrn) +- [e0e0fb4](https://github.com/mikeal/request/commit/e0e0fb451f17945a02203639e4836aa327b4e30b) hawk 0.9.0 (@hueniverse) +- [#456](https://github.com/mikeal/request/pull/456) hawk 0.9.0 (@hueniverse) +- [2f60bc2](https://github.com/mikeal/request/commit/2f60bc253ff6e28df58a33da24b710b6d506849f) Fixes #453 (@mikeal) +- [805b6e4](https://github.com/mikeal/request/commit/805b6e4fe3afeeb407b4fca2e34e9caabe30f747) Fixing hawk README to match new usage. (@mikeal) +- [8feb957](https://github.com/mikeal/request/commit/8feb957911083bce552d1898b7ffcaa87104cd21) Removing old logref code. (@mikeal) +- [fcf6d67](https://github.com/mikeal/request/commit/fcf6d6765247a2645a233d95468ade2960294074) Safe stringify. (@mikeal) +- [62455bc](https://github.com/mikeal/request/commit/62455bca81e8760f25a2bf1dec2b06c8e915de79) hawk 0.10 (@hueniverse) +- [c361b41](https://github.com/mikeal/request/commit/c361b4140e7e6e4fe2a8f039951b65d54af65f42) hawk 0.10 (@hueniverse) +- [fa1ef30](https://github.com/mikeal/request/commit/fa1ef30dcdac83b271ce38c71975df0ed96b08f7) Strip the UTF8 BOM from a UTF encoded response (@kppullin) +- [9d636c0](https://github.com/mikeal/request/commit/9d636c0b3e882742e15ba989d0c2413f95364680) if query params are empty, then request path shouldn't end with a '?' (@jaipandya) +- [#462](https://github.com/mikeal/request/pull/462) if query params are empty, then request path shouldn't end with a '?' (merges cleanly now) (@jaipandya) +- [#460](https://github.com/mikeal/request/pull/460) hawk 0.10.0 (@hueniverse) +- [#461](https://github.com/mikeal/request/pull/461) Strip the UTF8 BOM from a UTF encoded response (@kppullin) +- [6d29ed7](https://github.com/mikeal/request/commit/6d29ed72e34f3b2b6d8a5cfadd96dd26b3dd246d) Moving response handlers to onResponse. (@mikeal) +- [885d6eb](https://github.com/mikeal/request/commit/885d6ebeb6130c2ab7624304f4a01a898573390b) Using querystring library from visionmedia (@kbackowski) +- [#471](https://github.com/mikeal/request/pull/471) Using querystring library from visionmedia (@kbackowski) +- [346bb42](https://github.com/mikeal/request/commit/346bb42898c5804576d9e9b3adf40123260bf73b) On strictSSL set rejectUnauthorized. (@mikeal) +- [8a45365](https://github.com/mikeal/request/commit/8a453656a705d2fa98fbf9092b1600d2ddadbb5a) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [32cfd3c](https://github.com/mikeal/request/commit/32cfd3cf7b3f23c2b1d36c5ccb475cbb3a4693ff) Style changes. (@mikeal) +- [ec07ee2](https://github.com/mikeal/request/commit/ec07ee2d3eeb90b6d0ad9f6d7f3a36da72276841) Print debug logs NODE_DEBUG=request in environment (@isaacs) +- [681af64](https://github.com/mikeal/request/commit/681af644a2ebccad8bcccb75984f7f10f909b382) Flow data in v0.10-style streams (@isaacs) +- [#473](https://github.com/mikeal/request/pull/473) V0.10 compat (@isaacs) +- [f07a8ba](https://github.com/mikeal/request/commit/f07a8baebf7001addbc0f7d7c869adddc21768ce) Release. (@mikeal) +- [1f947a1](https://github.com/mikeal/request/commit/1f947a1d2728147fbf4f57aa361d0bedcebfc206) Rolling master version. (@mikeal) +- [7a217bb](https://github.com/mikeal/request/commit/7a217bbdced9a05a786fe6534ab52734df342d3e) Reinstate querystring for `unescape` (@shimaore) +- [b0b4ca9](https://github.com/mikeal/request/commit/b0b4ca913e119337e9313a157eee2f08f77ddc38) Test for `unescape` (@shimaore) +- [#475](https://github.com/mikeal/request/pull/475) Use `unescape` from `querystring` (@shimaore) +- [28fc741](https://github.com/mikeal/request/commit/28fc741fa958a9783031189964ef6f6d7e3f3264) Release. (@mikeal) +- [d3e28ef](https://github.com/mikeal/request/commit/d3e28ef7144da4d9f22f8fb475bd5aa6a80fb947) Rolling master version. (@mikeal) +- [8f8bb9e](https://github.com/mikeal/request/commit/8f8bb9ee8c4dcd9eb815249fbe2a7cf54f61b56f) Changing so if Accept header is explicitly set, sending json does not overwrite. (@RoryH) +- [#479](https://github.com/mikeal/request/pull/479) Changing so if Accept header is explicitly set, sending json does not ov... (@RoryH) +- [7694372](https://github.com/mikeal/request/commit/7694372f3dc9d57ac29ca7ee5c00146aa5e1e747) Proper version for latest. (@mikeal) +- [aa208cf](https://github.com/mikeal/request/commit/aa208cf5c682262529d749f592db147182cacfaf) 0.8+ only now (@mikeal) +- [16b5ab9](https://github.com/mikeal/request/commit/16b5ab9151823067b05b382241483ef10811c3e1) Upgrading qs. (@mikeal) +- [7d10c1e](https://github.com/mikeal/request/commit/7d10c1e83b4663f592c773e7fece83435585a06f) Merge branch 'master' of github.com:mikeal/request (@mikeal) +- [b8ca4b4](https://github.com/mikeal/request/commit/b8ca4b474b8215cab44ef8ef789303571b3d016f) pumping hawk version. (@mikeal) +- [9c0e484](https://github.com/mikeal/request/commit/9c0e48430e3a9de8715e77c07c98301399eaf6e3) release (@mikeal) +- [a9f1896](https://github.com/mikeal/request/commit/a9f189697e2a813bee9bff31de32a25e99e55cf2) rolling master version. (@mikeal) +- [560a1f8](https://github.com/mikeal/request/commit/560a1f8b927099e44b75274375a690df2a05de67) Set content-type on input. (@mikeal) +- [5fec436](https://github.com/mikeal/request/commit/5fec436b6602bc8c76133664bca23e98f511b096) Release. (@mikeal) +- [88d8d5b](https://github.com/mikeal/request/commit/88d8d5bc80679b78a39cab8e6d8295728a0a150d) Rolling version. (@mikeal) +- [d05b6ba](https://github.com/mikeal/request/commit/d05b6ba72702c2411b4627d4d89190a5f2aba562) Empty body must be passed as empty string, exclude JSON case (@Olegas) +- [#490](https://github.com/mikeal/request/pull/490) Empty response body (3-rd argument) must be passed to callback as an empty string (@Olegas) +- [8aa13cd](https://github.com/mikeal/request/commit/8aa13cd5b5e22b24466ef0e59fa8b5f1d0f0795a) Added redirect event (@Cauldrath) +- [4d63a04](https://github.com/mikeal/request/commit/4d63a042553c90718bf0b90652921b26c52dcb31) Moving response emit above setHeaders on destination streams (@kenperkins) +- [#498](https://github.com/mikeal/request/pull/498) Moving response emit above setHeaders on destination streams (@kenperkins) +- [c40993f](https://github.com/mikeal/request/commit/c40993fc987b1a8a3cb08cd5699b2f1b2bd4b28b) Fix a regression introduced by cba36ce6 (@nylen) +- [edc2e17](https://github.com/mikeal/request/commit/edc2e17e8154239efa6bd2914435798c18882635) Don't delete headers when retrying a request with proper authentication (@nylen) +- [a375ac1](https://github.com/mikeal/request/commit/a375ac15460f4f3b679f4418d7fc467a5cc94499) Refactor and expand basic auth tests (@nylen) +- [9bc28bf](https://github.com/mikeal/request/commit/9bc28bf912fb0afdd14b36b0ccbafb185a32546a) Cleanup whitespace. (@mikeal) +- [9a35cd2](https://github.com/mikeal/request/commit/9a35cd2248d9492b099c7ee46d68ca017b6a701c) Fix basic auth for passwords that contain colons (@tonistiigi) +- [f724810](https://github.com/mikeal/request/commit/f724810c7b9f82fa1423d0a4d19fcb5aaca98137) Honor the .strictSSL option when using proxies (tunnel-agent) (@jhs) +- [95a2558](https://github.com/mikeal/request/commit/95a25580375be1b9c39cc2e88a36a8387395bc13) Add HTTP Signature support. (@davidlehn) +- [921c973](https://github.com/mikeal/request/commit/921c973015721ee0f92ed670f5e88bca057104cc) * Make password optional to support the format: http://username@hostname/ +- [2759ebb](https://github.com/mikeal/request/commit/2759ebbe07e8563fd3ded698d2236309fb28176b) add 'localAddress' support (@yyfrankyy) +- [#513](https://github.com/mikeal/request/pull/513) add 'localAddress' support (@yyfrankyy) +- [#512](https://github.com/mikeal/request/pull/512) Make password optional to support the format: http://username@hostname/ (@pajato1) +- [#508](https://github.com/mikeal/request/pull/508) Honor the .strictSSL option when using proxies (tunnel-agent) (@iriscouch) +- [5f036e6](https://github.com/mikeal/request/commit/5f036e6f5d3102a89e5401a53090a0627a7850a8) Conflicts: index.js (@nylen) +- [89d2602](https://github.com/mikeal/request/commit/89d2602ef4e3a4e6e51284f6a29b5767c79ffaba) Conflicts: README.md (@davidlehn) +- [#502](https://github.com/mikeal/request/pull/502) Fix POST (and probably other) requests that are retried after 401 Unauthorized (@nylen) +- [eb3e033](https://github.com/mikeal/request/commit/eb3e033170403832fe7070955db32112ec46005f) Merge branch 'master' of git://github.com/mikeal/request (@davidlehn) +- [#510](https://github.com/mikeal/request/pull/510) Add HTTP Signature support. (@digitalbazaar) +- [227d998](https://github.com/mikeal/request/commit/227d9985426214b6ac68702933346000298d7790) Update the internal path variable when querystring is changed (@jblebrun) +- [#519](https://github.com/mikeal/request/pull/519) Update internal path state on post-creation QS changes (@incredible-labs) +- [428b9c1](https://github.com/mikeal/request/commit/428b9c1ad9831b7dfd6cec4ce68df358590c6d65) Fixing test-tunnel.js (@noway421) +- [2417599](https://github.com/mikeal/request/commit/24175993f6c362f7fca5965feb0a11756f00baf3) Improving test-localAddress.js (@noway421) +- [#520](https://github.com/mikeal/request/pull/520) Fixing test-tunnel.js (@noway421) +- [1e37f1b](https://github.com/mikeal/request/commit/1e37f1bea45174e09e6450bc71dfc081c8cd94de) Some explaining comments (@noway421) +- [909b024](https://github.com/mikeal/request/commit/909b024619c9e47f615749661d610cccd8421d80) Updating dependencies (@noway421) +- [#523](https://github.com/mikeal/request/pull/523) Updating dependencies (@noway421) +- [47191e1](https://github.com/mikeal/request/commit/47191e1a5e29714fb0c5f8b2162b2971570df644) 2.17.0 (@mikeal) +- [14def5a](https://github.com/mikeal/request/commit/14def5af5903d03f66bd6c9be534e6b76f47c063) 2.18.0 (@mikeal) +- [56fd6b7](https://github.com/mikeal/request/commit/56fd6b7ec6da162894df0809126d688f30900d25) 2.18.1 (@mikeal) +- [37dd689](https://github.com/mikeal/request/commit/37dd68989670f8937b537579a4299d9649b8aa16) Fixing dep. (@mikeal) +- [dd7209a](https://github.com/mikeal/request/commit/dd7209a84dd40afe87db31c6ab66885e2015cb8f) 2.19.0 (@mikeal) +- [62f3b92](https://github.com/mikeal/request/commit/62f3b9203690d4ad34486fc506fc78a1c9971e03) 2.19.1 (@mikeal) +- [74c6b2e](https://github.com/mikeal/request/commit/74c6b2e315872980ee9a9a000d25e724138f28b1) Adding test for onelineproxy. (@mikeal) +- [2a01cc0](https://github.com/mikeal/request/commit/2a01cc082f544647f7176a992e02668519a694be) Fixing onelineproxy. (@mikeal) +- [8b4c920](https://github.com/mikeal/request/commit/8b4c9203adb372f2ee99b1b012406b482b27c68d) 2.20.0 (@mikeal) +- [d8d4a33](https://github.com/mikeal/request/commit/d8d4a3311d8d31df88fa8a2ab3265872e5cb97ae) 2.20.1 (@mikeal) +- [5937012](https://github.com/mikeal/request/commit/59370123b22e8c971e4ee48c3d0caf920d890bda) dependencies versions bump (@jodaka) +- [#529](https://github.com/mikeal/request/pull/529) dependencies versions bump (@jodaka) +- [#521](https://github.com/mikeal/request/pull/521) Improving test-localAddress.js (@noway421) +- [#503](https://github.com/mikeal/request/pull/503) Fix basic auth for passwords that contain colons (@tonistiigi) +- [#497](https://github.com/mikeal/request/pull/497) Added redirect event (@Cauldrath) +- [297a9ea](https://github.com/mikeal/request/commit/297a9ea827655e5fb406a86907bb0d89b01deae8) fix typo (@fredericosilva) +- [#532](https://github.com/mikeal/request/pull/532) fix typo (@fredericosilva) +- [3691db5](https://github.com/mikeal/request/commit/3691db5a2d0981d4aeabfda5b988a5c69074e187) Allow explicitly empty user field for basic authentication. (@mikeando) +- [#536](https://github.com/mikeal/request/pull/536) Allow explicitly empty user field for basic authentication. (@mikeando) +- [5d36e32](https://github.com/mikeal/request/commit/5d36e324047f79cbbf3bb9b71fef633f02b36367) 2.21.0 (@mikeal) +- [9bd98d6](https://github.com/mikeal/request/commit/9bd98d6052f222aa348635c1acb2e2c99eed0f8c) 2.21.1 (@mikeal) +- [a918e04](https://github.com/mikeal/request/commit/a918e04a8d767a2948567ea29ed3fdd1650c16b1) The exported request function doesn't have an auth method (@tschaub) +- [1ebe1ac](https://github.com/mikeal/request/commit/1ebe1ac2f78e8a6149c03ce68fcb23d56df2316e) exposing Request class (@regality) +- [#542](https://github.com/mikeal/request/pull/542) Expose Request class (@ifit) +- [467573d](https://github.com/mikeal/request/commit/467573d17b4db5f93ed425ace0594370a7820c7c) Update http-signatures version. (@davidlehn) +- [#541](https://github.com/mikeal/request/pull/541) The exported request function doesn't have an auth method (@tschaub) +- [3040bbe](https://github.com/mikeal/request/commit/3040bbe5de846811151dab8dc09944acc93a338e) Fix redirections, (@criloz) +- [#564](https://github.com/mikeal/request/pull/564) Fix redirections (@NebTex) +- [397b435](https://github.com/mikeal/request/commit/397b4350fcf885460d7dced94cf1db1f5c167f80) handle ciphers and secureOptions in agentOptions (@SamPlacette) +- [65a2778](https://github.com/mikeal/request/commit/65a27782db7d2798b6490ea08efacb8f3b0a401c) tests and fix for null agentOptions case (@SamPlacette) +- [#568](https://github.com/mikeal/request/pull/568) use agentOptions to create agent when specified in request (@SamPlacette) +- [c116920](https://github.com/mikeal/request/commit/c116920a2cbef25afe2e1bbcf4df074e1e2f9dbb) Let's see how we do with only the main guard. (@mikeal) +- [f54a335](https://github.com/mikeal/request/commit/f54a3358119298634a7b0c29a21bf1471fc23d98) Fix spelling of "ignoring." (@bigeasy) +- [5cd215f](https://github.com/mikeal/request/commit/5cd215f327e113dc6c062634e405c577986cfd3c) Change isUrl regex to accept mixed case (@lexander) +- [02c8e74](https://github.com/mikeal/request/commit/02c8e749360a47d45e3e7b51b7f751fe498d2f25) #583 added tests for isUrl regex change. (@lexander) +- [#581](https://github.com/mikeal/request/pull/581) Fix spelling of "ignoring." (@bigeasy) +- [#544](https://github.com/mikeal/request/pull/544) Update http-signature version. (@digitalbazaar) +- [e77746b](https://github.com/mikeal/request/commit/e77746bf42e974dc91a84d03f44f750dd7ee0989) global cookie jar disabled by default, send jar: true to enable. (@threepointone) +- [46015ac](https://github.com/mikeal/request/commit/46015ac8d5b74f8107a6ec9fd07c133f46c5d833) 2.22.0 (@mikeal) +- [e5da4a5](https://github.com/mikeal/request/commit/e5da4a5e1a20bf4f23681f7b996f22c5fadae91d) 2.22.1 (@mikeal) +- [#587](https://github.com/mikeal/request/pull/587) Global cookie jar disabled by default (@threepointone) +- [fac9da1](https://github.com/mikeal/request/commit/fac9da1cc426bf0a4bcc5f0b7d0d0aea8b1cce38) Prevent setting headers after they are sent (@wpreul) +- [#589](https://github.com/mikeal/request/pull/589) Prevent setting headers after they are sent (@wpreul) +- [bc1537a](https://github.com/mikeal/request/commit/bc1537ab79064cea532b0d14110ce4e49a663bde) Emit complete event when there is no callback +- [de8508e](https://github.com/mikeal/request/commit/de8508e9feac10563596aeee26727567b3c2e33c) Added check to see if the global pool is being used before using the global agent (@Cauldrath) +- [03441ef](https://github.com/mikeal/request/commit/03441ef919e51a742aaf9e168d917e97e2d9eb6b) 2.23.0 (@mikeal) diff --git a/packages/wekan-request/LICENSE b/packages/wekan-request/LICENSE new file mode 100644 index 000000000..a4a9aee0c --- /dev/null +++ b/packages/wekan-request/LICENSE @@ -0,0 +1,55 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/wekan-request/README.md b/packages/wekan-request/README.md new file mode 100644 index 000000000..4b416932e --- /dev/null +++ b/packages/wekan-request/README.md @@ -0,0 +1,421 @@ +# Request -- Simplified HTTP client + +[![NPM](https://nodei.co/npm/request.png)](https://nodei.co/npm/request/) + +## Super simple to use + +Request is designed to be the simplest way possible to make http calls. It supports HTTPS and follows redirects by default. + +```javascript +var request = require('request'); +request('http://www.google.com', function (error, response, body) { + if (!error && response.statusCode == 200) { + console.log(body) // Print the google web page. + } +}) +``` + +## Streaming + +You can stream any response to a file stream. + +```javascript +request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png')) +``` + +You can also stream a file to a PUT or POST request. This method will also check the file extension against a mapping of file extensions to content-types (in this case `application/json`) and use the proper `content-type` in the PUT request (if the headers don’t already provide one). + +```javascript +fs.createReadStream('file.json').pipe(request.put('http://mysite.com/obj.json')) +``` + +Request can also `pipe` to itself. When doing so, `content-type` and `content-length` are preserved in the PUT headers. + +```javascript +request.get('http://google.com/img.png').pipe(request.put('http://mysite.com/img.png')) +``` + +Now let’s get fancy. + +```javascript +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + if (req.method === 'PUT') { + req.pipe(request.put('http://mysite.com/doodle.png')) + } else if (req.method === 'GET' || req.method === 'HEAD') { + request.get('http://mysite.com/doodle.png').pipe(resp) + } + } +}) +``` + +You can also `pipe()` from `http.ServerRequest` instances, as well as to `http.ServerResponse` instances. The HTTP method, headers, and entity-body data will be sent. Which means that, if you don't really care about security, you can do: + +```javascript +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + var x = request('http://mysite.com/doodle.png') + req.pipe(x) + x.pipe(resp) + } +}) +``` + +And since `pipe()` returns the destination stream in ≥ Node 0.5.x you can do one line proxying. :) + +```javascript +req.pipe(request('http://mysite.com/doodle.png')).pipe(resp) +``` + +Also, none of this new functionality conflicts with requests previous features, it just expands them. + +```javascript +var r = request.defaults({'proxy':'http://localproxy.com'}) + +http.createServer(function (req, resp) { + if (req.url === '/doodle.png') { + r.get('http://google.com/doodle.png').pipe(resp) + } +}) +``` + +You can still use intermediate proxies, the requests will still follow HTTP forwards, etc. + +## UNIX Socket + +`request` supports the `unix://` protocol for all requests. The path is assumed to be absolute to the root of the host file system. + +HTTP paths are extracted from the supplied URL by testing each level of the full URL against net.connect for a socket response. + +Thus the following request will GET `/httppath` from the HTTP server listening on `/tmp/unix.socket` + +```javascript +request.get('unix://tmp/unix.socket/httppath') +``` + +## Forms + +`request` supports `application/x-www-form-urlencoded` and `multipart/form-data` form uploads. For `multipart/related` refer to the `multipart` API. + +URL-encoded forms are simple. + +```javascript +request.post('http://service.com/upload', {form:{key:'value'}}) +// or +request.post('http://service.com/upload').form({key:'value'}) +``` + +For `multipart/form-data` we use the [form-data](https://github.com/felixge/node-form-data) library by [@felixge](https://github.com/felixge). You don’t need to worry about piping the form object or setting the headers, `request` will handle that for you. + +```javascript +var r = request.post('http://service.com/upload', function optionalCallback (err, httpResponse, body) { + if (err) { + return console.error('upload failed:', err); + } + console.log('Upload successful! Server responded with:', body); +}) +var form = r.form() +form.append('my_field', 'my_value') +form.append('my_buffer', new Buffer([1, 2, 3])) +form.append('my_file', fs.createReadStream(path.join(__dirname, 'doodle.png'))) +form.append('remote_file', request('http://google.com/doodle.png')) + +// Just like always, `r` is a writable stream, and can be used as such (you have until nextTick to pipe it, etc.) +// Alternatively, you can provide a callback (that's what this example does-- see `optionalCallback` above). +``` + +## HTTP Authentication + +```javascript +request.get('http://some.server.com/').auth('username', 'password', false); +// or +request.get('http://some.server.com/', { + 'auth': { + 'user': 'username', + 'pass': 'password', + 'sendImmediately': false + } +}); +// or +request.get('http://some.server.com/').auth(null, null, true, 'bearerToken'); +// or +request.get('http://some.server.com/', { + 'auth': { + 'bearer': 'bearerToken' + } +}); +``` + +If passed as an option, `auth` should be a hash containing values `user` || `username`, `pass` || `password`, and `sendImmediately` (optional). The method form takes parameters `auth(username, password, sendImmediately)`. + +`sendImmediately` defaults to `true`, which causes a basic authentication header to be sent. If `sendImmediately` is `false`, then `request` will retry with a proper authentication header after receiving a `401` response from the server (which must contain a `WWW-Authenticate` header indicating the required authentication method). + +Note that you can also use for basic authentication a trick using the URL itself, as specified in [RFC 1738](http://www.ietf.org/rfc/rfc1738.txt). +Simply pass the `user:password` before the host with an `@` sign. + +```javascript +var username = 'username', + password = 'password', + url = 'http://' + username + ':' + password + '@some.server.com'; + +request({url: url}, function (error, response, body) { + // Do more stuff with 'body' here +}); +``` + +Digest authentication is supported, but it only works with `sendImmediately` set to `false`; otherwise `request` will send basic authentication on the initial request, which will probably cause the request to fail. + +Bearer authentication is supported, and is activated when the `bearer` value is available. The value may be either a `String` or a `Function` returning a `String`. Using a function to supply the bearer token is particularly useful if used in conjuction with `defaults` to allow a single function to supply the last known token at the time or sending a request or to compute one on the fly. + +## OAuth Signing + +```javascript +// Twitter OAuth +var qs = require('querystring') + , oauth = + { callback: 'http://mysite.com/callback/' + , consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + } + , url = 'https://api.twitter.com/oauth/request_token' + ; +request.post({url:url, oauth:oauth}, function (e, r, body) { + // Ideally, you would take the body in the response + // and construct a URL that a user clicks on (like a sign in button). + // The verifier is only available in the response after a user has + // verified with twitter that they are authorizing your app. + var access_token = qs.parse(body) + , oauth = + { consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + , token: access_token.oauth_token + , verifier: access_token.oauth_verifier + } + , url = 'https://api.twitter.com/oauth/access_token' + ; + request.post({url:url, oauth:oauth}, function (e, r, body) { + var perm_token = qs.parse(body) + , oauth = + { consumer_key: CONSUMER_KEY + , consumer_secret: CONSUMER_SECRET + , token: perm_token.oauth_token + , token_secret: perm_token.oauth_token_secret + } + , url = 'https://api.twitter.com/1.1/users/show.json?' + , params = + { screen_name: perm_token.screen_name + , user_id: perm_token.user_id + } + ; + url += qs.stringify(params) + request.get({url:url, oauth:oauth, json:true}, function (e, r, user) { + console.log(user) + }) + }) +}) +``` + +### Custom HTTP Headers + +HTTP Headers, such as `User-Agent`, can be set in the `options` object. +In the example below, we call the github API to find out the number +of stars and forks for the request repository. This requires a +custom `User-Agent` header as well as https. + +```javascript +var request = require('request'); + +var options = { + url: 'https://api.github.com/repos/mikeal/request', + headers: { + 'User-Agent': 'request' + } +}; + +function callback(error, response, body) { + if (!error && response.statusCode == 200) { + var info = JSON.parse(body); + console.log(info.stargazers_count + " Stars"); + console.log(info.forks_count + " Forks"); + } +} + +request(options, callback); +``` + +### request(options, callback) + +The first argument can be either a `url` or an `options` object. The only required option is `uri`; all others are optional. + +* `uri` || `url` - fully qualified uri or a parsed url object from `url.parse()` +* `qs` - object containing querystring values to be appended to the `uri` +* `method` - http method (default: `"GET"`) +* `headers` - http headers (default: `{}`) +* `body` - entity body for PATCH, POST and PUT requests. Must be a `Buffer` or `String`. +* `form` - when passed an object or a querystring, this sets `body` to a querystring representation of value, and adds `Content-type: application/x-www-form-urlencoded; charset=utf-8` header. When passed no options, a `FormData` instance is returned (and is piped to request). +* `auth` - A hash containing values `user` || `username`, `pass` || `password`, and `sendImmediately` (optional). See documentation above. +* `json` - sets `body` but to JSON representation of value and adds `Content-type: application/json` header. Additionally, parses the response body as JSON. +* `multipart` - (experimental) array of objects which contains their own headers and `body` attribute. Sends `multipart/related` request. See example below. +* `followRedirect` - follow HTTP 3xx responses as redirects (default: `true`) +* `followAllRedirects` - follow non-GET HTTP 3xx responses as redirects (default: `false`) +* `maxRedirects` - the maximum number of redirects to follow (default: `10`) +* `encoding` - Encoding to be used on `setEncoding` of response data. If `null`, the `body` is returned as a `Buffer`. +* `pool` - A hash object containing the agents for these requests. If omitted, the request will use the global pool (which is set to node's default `maxSockets`) +* `pool.maxSockets` - Integer containing the maximum amount of sockets in the pool. +* `timeout` - Integer containing the number of milliseconds to wait for a request to respond before aborting the request +* `proxy` - An HTTP proxy to be used. Supports proxy Auth with Basic Auth, identical to support for the `url` parameter (by embedding the auth info in the `uri`) +* `oauth` - Options for OAuth HMAC-SHA1 signing. See documentation above. +* `hawk` - Options for [Hawk signing](https://github.com/hueniverse/hawk). The `credentials` key must contain the necessary signing info, [see hawk docs for details](https://github.com/hueniverse/hawk#usage-example). +* `strictSSL` - If `true`, requires SSL certificates be valid. **Note:** to use your own certificate authority, you need to specify an agent that was created with that CA as an option. +* `jar` - If `true` and `tough-cookie` is installed, remember cookies for future use (or define your custom cookie jar; see examples section) +* `aws` - `object` containing AWS signing information. Should have the properties `key`, `secret`. Also requires the property `bucket`, unless you’re specifying your `bucket` as part of the path, or the request doesn’t use a bucket (i.e. GET Services) +* `httpSignature` - Options for the [HTTP Signature Scheme](https://github.com/joyent/node-http-signature/blob/master/http_signing.md) using [Joyent's library](https://github.com/joyent/node-http-signature). The `keyId` and `key` properties must be specified. See the docs for other options. +* `localAddress` - Local interface to bind for network connections. +* `gzip` - If `true`, add an `Accept-Encoding` header to request compressed content encodings from the server (if not already present) and decode supported content encodings in the response. + + +The callback argument gets 3 arguments: + +1. An `error` when applicable (usually from [`http.ClientRequest`](http://nodejs.org/api/http.html#http_class_http_clientrequest) object) +2. An [`http.IncomingMessage`](http://nodejs.org/api/http.html#http_http_incomingmessage) object +3. The third is the `response` body (`String` or `Buffer`, or JSON object if the `json` option is supplied) + +## Convenience methods + +There are also shorthand methods for different HTTP METHODs and some other conveniences. + +### request.defaults(options) + +This method returns a wrapper around the normal request API that defaults to whatever options you pass in to it. + +### request.put + +Same as `request()`, but defaults to `method: "PUT"`. + +```javascript +request.put(url) +``` + +### request.patch + +Same as `request()`, but defaults to `method: "PATCH"`. + +```javascript +request.patch(url) +``` + +### request.post + +Same as `request()`, but defaults to `method: "POST"`. + +```javascript +request.post(url) +``` + +### request.head + +Same as request() but defaults to `method: "HEAD"`. + +```javascript +request.head(url) +``` + +### request.del + +Same as `request()`, but defaults to `method: "DELETE"`. + +```javascript +request.del(url) +``` + +### request.get + +Same as `request()` (for uniformity). + +```javascript +request.get(url) +``` +### request.cookie + +Function that creates a new cookie. + +```javascript +request.cookie('cookie_string_here') +``` +### request.jar + +Function that creates a new cookie jar. + +```javascript +request.jar() +``` + + +## Examples: + +```javascript + var request = require('request') + , rand = Math.floor(Math.random()*100000000).toString() + ; + request( + { method: 'PUT' + , uri: 'http://mikeal.iriscouch.com/testjs/' + rand + , multipart: + [ { 'content-type': 'application/json' + , body: JSON.stringify({foo: 'bar', _attachments: {'message.txt': {follows: true, length: 18, 'content_type': 'text/plain' }}}) + } + , { body: 'I am an attachment' } + ] + } + , function (error, response, body) { + if(response.statusCode == 201){ + console.log('document saved as: http://mikeal.iriscouch.com/testjs/'+ rand) + } else { + console.log('error: '+ response.statusCode) + console.log(body) + } + } + ) +``` + +Cookies are disabled by default (else, they would be used in subsequent requests). To enable cookies, set `jar` to `true` (either in `defaults` or `options`) and install `tough-cookie`. + +```javascript +var request = request.defaults({jar: true}) +request('http://www.google.com', function () { + request('http://images.google.com') +}) +``` + +To use a custom cookie jar (instead of `request`’s global cookie jar), set `jar` to an instance of `request.jar()` (either in `defaults` or `options`) + +```javascript +var j = request.jar() +var request = request.defaults({jar:j}) +request('http://www.google.com', function () { + request('http://images.google.com') +}) +``` + +OR + +```javascript +// `npm install --save tough-cookie` before this works +var j = request.jar() +var cookie = request.cookie('your_cookie_here') +j.setCookie(cookie, uri); +request({url: 'http://www.google.com', jar: j}, function () { + request('http://images.google.com') +}) +``` + +To inspect your cookie jar after a request + +```javascript +var j = request.jar() +request({url: 'http://www.google.com', jar: j}, function () { + var cookie_string = j.getCookieString(uri); // "key1=value1; key2=value2; ..." + var cookies = j.getCookies(uri); + // [{key: 'key1', value: 'value1', domain: "www.google.com", ...}, ...] +}) +``` diff --git a/packages/wekan-request/index.js b/packages/wekan-request/index.js new file mode 100755 index 000000000..4b95008d1 --- /dev/null +++ b/packages/wekan-request/index.js @@ -0,0 +1,166 @@ +// Copyright 2010-2012 Mikeal Rogers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var cookies = require('./lib/cookies') + , copy = require('./lib/copy') + , Request = require('./request') + , util = require('util') + ; + + + +// organize params for patch, post, put, head, del +function initParams(uri, options, callback) { + var opts; + if ((typeof options === 'function') && !callback) callback = options + if (options && typeof options === 'object') { + opts = util._extend({}, options); + opts.uri = uri + } else if (typeof uri === 'string') { + opts = {uri:uri} + } else { + opts = util._extend({}, uri); + uri = opts.uri + } + + return { uri: uri, options: opts, callback: callback } +} + +function request (uri, options, callback) { + var opts; + if (typeof uri === 'undefined') throw new Error('undefined is not a valid uri or options object.') + if ((typeof options === 'function') && !callback) callback = options + if (options && typeof options === 'object') { + opts = util._extend({}, options); + opts.uri = uri + } else if (typeof uri === 'string') { + opts = {uri:uri} + } else { + opts = util._extend({}, uri); + } + + if (callback) opts.callback = callback + var r = new Request(opts) + return r +} + +module.exports = request + +request.Request = Request; + +request.debug = process.env.NODE_DEBUG && /request/.test(process.env.NODE_DEBUG) + +request.initParams = initParams + +request.defaults = function (options, requester) { + var def = function (method) { + var d = function (uri, opts, callback) { + var params = initParams(uri, opts, callback) + Object.keys(options).forEach(function (key) { + if (key !== 'headers' && params.options[key] === undefined) { + params.options[key] = options[key] + } + }) + if (options.headers) { + var headers = {} + util._extend(headers, options.headers) + util._extend(headers, params.options.headers) + params.options.headers = headers + } + if(typeof requester === 'function') { + if(method === request) { + method = requester + } else { + params.options._requester = requester + } + } + return method(params.options, params.callback) + } + return d + } + var de = def(request) + de.get = def(request.get) + de.patch = def(request.patch) + de.post = def(request.post) + de.put = def(request.put) + de.head = def(request.head) + de.del = def(request.del) + de.cookie = def(request.cookie) + de.jar = request.jar + return de +} + +function requester(params) { + if(typeof params.options._requester === 'function') { + return params.options._requester + } else { + return request + } +} + +request.forever = function (agentOptions, optionsArg) { + var options = {} + if (optionsArg) { + for (var option in optionsArg) { + options[option] = optionsArg[option] + } + } + if (agentOptions) options.agentOptions = agentOptions + options.forever = true + return request.defaults(options) +} + +request.get = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'GET' + return requester(params)(params.uri || null, params.options, params.callback) +} +request.post = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'POST' + return requester(params)(params.uri || null, params.options, params.callback) +} +request.put = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'PUT' + return requester(params)(params.uri || null, params.options, params.callback) +} +request.patch = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'PATCH' + return requester(params)(params.uri || null, params.options, params.callback) +} +request.head = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'HEAD' + if (params.options.body || + params.options.requestBodyStream || + (params.options.json && typeof params.options.json !== 'boolean') || + params.options.multipart) { + throw new Error("HTTP HEAD requests MUST NOT include a request body.") + } + + return requester(params)(params.uri || null, params.options, params.callback) +} +request.del = function (uri, options, callback) { + var params = initParams(uri, options, callback) + params.options.method = 'DELETE' + return requester(params)(params.uri || null, params.options, params.callback) +} +request.jar = function () { + return cookies.jar(); +} +request.cookie = function (str) { + return cookies.parse(str); +} diff --git a/packages/wekan-request/lib/cookies.js b/packages/wekan-request/lib/cookies.js new file mode 100644 index 000000000..7e61c62bc --- /dev/null +++ b/packages/wekan-request/lib/cookies.js @@ -0,0 +1,40 @@ +var optional = require('./optional') + , tough = optional('tough-cookie') + , Cookie = tough && tough.Cookie + , CookieJar = tough && tough.CookieJar + ; + +exports.parse = function(str) { + if (str && str.uri) str = str.uri + if (typeof str !== 'string') throw new Error("The cookie function only accepts STRING as param") + if (!Cookie) { + return null; + } + return Cookie.parse(str) +}; + +// Adapt the sometimes-Async api of tough.CookieJar to our requirements +function RequestJar() { + this._jar = new CookieJar(); +} +RequestJar.prototype.setCookie = function(cookieOrStr, uri, options) { + return this._jar.setCookieSync(cookieOrStr, uri, options || {}); +}; +RequestJar.prototype.getCookieString = function(uri) { + return this._jar.getCookieStringSync(uri); +}; +RequestJar.prototype.getCookies = function(uri) { + return this._jar.getCookiesSync(uri); +}; + +exports.jar = function() { + if (!CookieJar) { + // tough-cookie not loaded, return a stub object: + return { + setCookie: function(){}, + getCookieString: function(){}, + getCookies: function(){} + }; + } + return new RequestJar(); +}; diff --git a/packages/wekan-request/lib/copy.js b/packages/wekan-request/lib/copy.js new file mode 100644 index 000000000..56831ff80 --- /dev/null +++ b/packages/wekan-request/lib/copy.js @@ -0,0 +1,8 @@ +module.exports = +function copy (obj) { + var o = {} + Object.keys(obj).forEach(function (i) { + o[i] = obj[i] + }) + return o +} \ No newline at end of file diff --git a/packages/wekan-request/lib/debug.js b/packages/wekan-request/lib/debug.js new file mode 100644 index 000000000..fa27b24b6 --- /dev/null +++ b/packages/wekan-request/lib/debug.js @@ -0,0 +1,7 @@ +var util = require('util') + +module.exports = +function debug () { + if (/\brequest\b/.test(process.env.NODE_DEBUG)) + console.error('REQUEST %s', util.format.apply(util, arguments)) +} diff --git a/packages/wekan-request/lib/getSafe.js b/packages/wekan-request/lib/getSafe.js new file mode 100644 index 000000000..28e07ea56 --- /dev/null +++ b/packages/wekan-request/lib/getSafe.js @@ -0,0 +1,34 @@ +// Safe toJSON +module.exports = +function getSafe (self, uuid) { + if (typeof self === 'object' || typeof self === 'function') var safe = {} + if (Array.isArray(self)) var safe = [] + + var recurse = [] + + Object.defineProperty(self, uuid, {}) + + var attrs = Object.keys(self).filter(function (i) { + if (i === uuid) return false + if ( (typeof self[i] !== 'object' && typeof self[i] !== 'function') || self[i] === null) return true + return !(Object.getOwnPropertyDescriptor(self[i], uuid)) + }) + + + for (var i=0;i", + "repository": { + "type": "git", + "url": "https://github.com/mikeal/request.git" + }, + "bugs": { + "url": "http://github.com/mikeal/request/issues" + }, + "license": "Apache-2.0", + "engines": [ + "node >= 0.8.0" + ], + "main": "index.js", + "dependencies": { + "qs": ">=0.6.4", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "forever-agent": "~0.5.0", + "node-uuid": "~1.4.0" + }, + "optionalDependencies": { + "tough-cookie": ">=0.12.0", + "form-data": "~0.1.0", + "tunnel-agent": "~0.4.0", + "http-signature": "~0.10.0", + "oauth-sign": "~0.3.0", + "hawk": "1.1.1", + "aws-sign2": "~0.5.0", + "stringstream": "~0.0.4" + }, + "scripts": { + "test": "node tests/run.js" + } +} diff --git a/packages/wekan-request/request.js b/packages/wekan-request/request.js new file mode 100644 index 000000000..90847b16c --- /dev/null +++ b/packages/wekan-request/request.js @@ -0,0 +1,1442 @@ +var optional = require('./lib/optional') + , http = require('http') + , https = optional('https') + , tls = optional('tls') + , url = require('url') + , util = require('util') + , stream = require('stream') + , qs = require('qs') + , querystring = require('querystring') + , crypto = require('crypto') + , zlib = require('zlib') + + , oauth = optional('oauth-sign') + , hawk = optional('hawk') + , aws = optional('aws-sign2') + , httpSignature = optional('http-signature') + , uuid = require('node-uuid') + , mime = require('mime-types') + , tunnel = optional('tunnel-agent') + , _safeStringify = require('json-stringify-safe') + , stringstream = optional('stringstream') + + , ForeverAgent = require('forever-agent') + , FormData = optional('form-data') + + , cookies = require('./lib/cookies') + , globalCookieJar = cookies.jar() + + , copy = require('./lib/copy') + , debug = require('./lib/debug') + , getSafe = require('./lib/getSafe') + , net = require('net') + ; + +function safeStringify (obj) { + var ret + try { ret = JSON.stringify(obj) } + catch (e) { ret = _safeStringify(obj) } + return ret +} + +var globalPool = {} +var isUrl = /^https?:|^unix:/ + + +// Hacky fix for pre-0.4.4 https +if (https && !https.Agent) { + https.Agent = function (options) { + http.Agent.call(this, options) + } + util.inherits(https.Agent, http.Agent) + https.Agent.prototype._getConnection = function (host, port, cb) { + var s = tls.connect(port, host, this.options, function () { + // do other checks here? + if (cb) cb() + }) + return s + } +} + +function isReadStream (rs) { + return rs.readable && rs.path && rs.mode; +} + +function toBase64 (str) { + return (new Buffer(str || "", "ascii")).toString("base64") +} + +function md5 (str) { + return crypto.createHash('md5').update(str).digest('hex') +} + +function Request (options) { + stream.Stream.call(this) + this.readable = true + this.writable = true + + if (typeof options === 'string') { + options = {uri:options} + } + + var reserved = Object.keys(Request.prototype) + for (var i in options) { + if (reserved.indexOf(i) === -1) { + this[i] = options[i] + } else { + if (typeof options[i] === 'function') { + delete options[i] + } + } + } + + if (options.method) { + this.explicitMethod = true + } + + this.canTunnel = options.tunnel !== false && tunnel; + + this.init(options) +} +util.inherits(Request, stream.Stream) +Request.prototype.init = function (options) { + // init() contains all the code to setup the request object. + // the actual outgoing request is not started until start() is called + // this function is called from both the constructor and on redirect. + var self = this + if (!options) options = {} + + if (!self.method) self.method = options.method || 'GET' + self.localAddress = options.localAddress + + debug(options) + if (!self.pool && self.pool !== false) self.pool = globalPool + self.dests = self.dests || [] + self.__isRequestRequest = true + + // Protect against double callback + if (!self._callback && self.callback) { + self._callback = self.callback + self.callback = function () { + if (self._callbackCalled) return // Print a warning maybe? + self._callbackCalled = true + self._callback.apply(self, arguments) + } + self.on('error', self.callback.bind()) + self.on('complete', self.callback.bind(self, null)) + } + + if (self.url && !self.uri) { + // People use this property instead all the time so why not just support it. + self.uri = self.url + delete self.url + } + + if (!self.uri) { + // this will throw if unhandled but is handleable when in a redirect + return self.emit('error', new Error("options.uri is a required argument")) + } else { + if (typeof self.uri == "string") self.uri = url.parse(self.uri) + } + + if (self.strictSSL === false) { + self.rejectUnauthorized = false + } + + if(!self.hasOwnProperty('proxy')) { + // check for HTTP(S)_PROXY environment variables + if(self.uri.protocol == "http:") { + self.proxy = process.env.HTTP_PROXY || process.env.http_proxy || null; + } else if(self.uri.protocol == "https:") { + self.proxy = process.env.HTTPS_PROXY || process.env.https_proxy || + process.env.HTTP_PROXY || process.env.http_proxy || null; + } + } + + if (self.proxy) { + if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy) + + // do the HTTP CONNECT dance using koichik/node-tunnel + if (http.globalAgent && self.uri.protocol === "https:" && self.canTunnel) { + var tunnelFn = self.proxy.protocol === "http:" + ? tunnel.httpsOverHttp : tunnel.httpsOverHttps + + var tunnelOptions = { proxy: { host: self.proxy.hostname + , port: +self.proxy.port + , proxyAuth: self.proxy.auth + , headers: { Host: self.uri.hostname + ':' + + (self.uri.port || self.uri.protocol === 'https:' ? 443 : 80) }} + , rejectUnauthorized: self.rejectUnauthorized + , ca: this.ca + , cert:this.cert + , key: this.key} + + self.agent = tunnelFn(tunnelOptions) + self.tunnel = true + } + } + + if (!self.uri.pathname) {self.uri.pathname = '/'} + + if (!self.uri.host && !self.protocol=='unix:') { + // Invalid URI: it may generate lot of bad errors, like "TypeError: Cannot call method 'indexOf' of undefined" in CookieJar + // Detect and reject it as soon as possible + var faultyUri = url.format(self.uri) + var message = 'Invalid URI "' + faultyUri + '"' + if (Object.keys(options).length === 0) { + // No option ? This can be the sign of a redirect + // As this is a case where the user cannot do anything (they didn't call request directly with this URL) + // they should be warned that it can be caused by a redirection (can save some hair) + message += '. This can be caused by a crappy redirection.' + } + self.emit('error', new Error(message)) + return // This error was fatal + } + + self._redirectsFollowed = self._redirectsFollowed || 0 + self.maxRedirects = (self.maxRedirects !== undefined) ? self.maxRedirects : 10 + self.followRedirect = (self.followRedirect !== undefined) ? self.followRedirect : true + self.followAllRedirects = (self.followAllRedirects !== undefined) ? self.followAllRedirects : false + if (self.followRedirect || self.followAllRedirects) + self.redirects = self.redirects || [] + + self.headers = self.headers ? copy(self.headers) : {} + + self.setHost = false + if (!self.hasHeader('host')) { + self.setHeader('host', self.uri.hostname) + if (self.uri.port) { + if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') && + !(self.uri.port === 443 && self.uri.protocol === 'https:') ) + self.setHeader('host', self.getHeader('host') + (':'+self.uri.port) ) + } + self.setHost = true + } + + self.jar(self._jar || options.jar) + + if (!self.uri.port) { + if (self.uri.protocol == 'http:') {self.uri.port = 80} + else if (self.uri.protocol == 'https:') {self.uri.port = 443} + } + + if (self.proxy && !self.tunnel) { + self.port = self.proxy.port + self.host = self.proxy.hostname + } else { + self.port = self.uri.port + self.host = self.uri.hostname + } + + self.clientErrorHandler = function (error) { + if (self._aborted) return + if (self.req && self.req._reusedSocket && error.code === 'ECONNRESET' + && self.agent.addRequestNoreuse) { + self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) } + self.start() + self.req.end() + return + } + if (self.timeout && self.timeoutTimer) { + clearTimeout(self.timeoutTimer) + self.timeoutTimer = null + } + self.emit('error', error) + } + + self._parserErrorHandler = function (error) { + if (this.res) { + if (this.res.request) { + this.res.request.emit('error', error) + } else { + this.res.emit('error', error) + } + } else { + this._httpMessage.emit('error', error) + } + } + + self._buildRequest = function(){ + var self = this; + + if (options.form) { + self.form(options.form) + } + + if (options.qs) self.qs(options.qs) + + if (self.uri.path) { + self.path = self.uri.path + } else { + self.path = self.uri.pathname + (self.uri.search || "") + } + + if (self.path.length === 0) self.path = '/' + + + // Auth must happen last in case signing is dependent on other headers + if (options.oauth) { + self.oauth(options.oauth) + } + + if (options.aws) { + self.aws(options.aws) + } + + if (options.hawk) { + self.hawk(options.hawk) + } + + if (options.httpSignature) { + self.httpSignature(options.httpSignature) + } + + if (options.auth) { + if (Object.prototype.hasOwnProperty.call(options.auth, 'username')) options.auth.user = options.auth.username + if (Object.prototype.hasOwnProperty.call(options.auth, 'password')) options.auth.pass = options.auth.password + + self.auth( + options.auth.user, + options.auth.pass, + options.auth.sendImmediately, + options.auth.bearer + ) + } + + if (self.gzip && !self.hasHeader('accept-encoding')) { + self.setHeader('accept-encoding', 'gzip') + } + + if (self.uri.auth && !self.hasHeader('authorization')) { + var authPieces = self.uri.auth.split(':').map(function(item){ return querystring.unescape(item) }) + self.auth(authPieces[0], authPieces.slice(1).join(':'), true) + } + if (self.proxy && self.proxy.auth && !self.hasHeader('proxy-authorization') && !self.tunnel) { + self.setHeader('proxy-authorization', "Basic " + toBase64(self.proxy.auth.split(':').map(function(item){ return querystring.unescape(item)}).join(':'))) + } + + + if (self.proxy && !self.tunnel) self.path = (self.uri.protocol + '//' + self.uri.host + self.path) + + if (options.json) { + self.json(options.json) + } else if (options.multipart) { + self.boundary = uuid() + self.multipart(options.multipart) + } + + if (self.body) { + var length = 0 + if (!Buffer.isBuffer(self.body)) { + if (Array.isArray(self.body)) { + for (var i = 0; i < self.body.length; i++) { + length += self.body[i].length + } + } else { + self.body = new Buffer(self.body) + length = self.body.length + } + } else { + length = self.body.length + } + if (length) { + if (!self.hasHeader('content-length')) self.setHeader('content-length', length) + } else { + throw new Error('Argument error, options.body.') + } + } + + var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol + , defaultModules = {'http:':http, 'https:':https, 'unix:':http} + , httpModules = self.httpModules || {} + ; + self.httpModule = httpModules[protocol] || defaultModules[protocol] + + if (!self.httpModule) return this.emit('error', new Error("Invalid protocol: " + protocol)) + + if (options.ca) self.ca = options.ca + + if (!self.agent) { + if (options.agentOptions) self.agentOptions = options.agentOptions + + if (options.agentClass) { + self.agentClass = options.agentClass + } else if (options.forever) { + self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL + } else { + self.agentClass = self.httpModule.Agent + } + } + + if (self.pool === false) { + self.agent = false + } else { + self.agent = self.agent || self.getAgent() + if (self.maxSockets) { + // Don't use our pooling if node has the refactored client + self.agent.maxSockets = self.maxSockets + } + if (self.pool.maxSockets) { + // Don't use our pooling if node has the refactored client + self.agent.maxSockets = self.pool.maxSockets + } + } + + self.on('pipe', function (src) { + if (self.ntick && self._started) throw new Error("You cannot pipe to this stream after the outbound request has started.") + self.src = src + if (isReadStream(src)) { + if (!self.hasHeader('content-type')) self.setHeader('content-type', mime.lookup(src.path)) + } else { + if (src.headers) { + for (var i in src.headers) { + if (!self.hasHeader(i)) { + self.setHeader(i, src.headers[i]) + } + } + } + if (self._json && !self.hasHeader('content-type')) + self.setHeader('content-type', 'application/json') + if (src.method && !self.explicitMethod) { + self.method = src.method + } + } + + // self.on('pipe', function () { + // console.error("You have already piped to this stream. Pipeing twice is likely to break the request.") + // }) + }) + + process.nextTick(function () { + if (self._aborted) return + + var end = function () { + if (self._form) { + self._form.pipe(self) + } + if (self.body) { + if (Array.isArray(self.body)) { + self.body.forEach(function (part) { + self.write(part) + }) + } else { + self.write(self.body) + } + self.end() + } else if (self.requestBodyStream) { + console.warn("options.requestBodyStream is deprecated, please pass the request object to stream.pipe.") + self.requestBodyStream.pipe(self) + } else if (!self.src) { + if (self.method !== 'GET' && typeof self.method !== 'undefined') { + self.setHeader('content-length', 0) + } + self.end() + } + } + + if (self._form && !self.hasHeader('content-length')) { + // Before ending the request, we had to compute the length of the whole form, asyncly + self.setHeaders(self._form.getHeaders()) + self._form.getLength(function (err, length) { + if (!err) { + self.setHeader('content-length', length) + } + end() + }) + } else { + end() + } + + self.ntick = true + }) + + } // End _buildRequest + + self._handleUnixSocketURI = function(self){ + // Parse URI and extract a socket path (tested as a valid socket using net.connect), and a http style path suffix + // Thus http requests can be made to a socket using the uri unix://tmp/my.socket/urlpath + // and a request for '/urlpath' will be sent to the unix socket at /tmp/my.socket + + self.unixsocket = true; + + var full_path = self.uri.href.replace(self.uri.protocol+'/', ''); + + var lookup = full_path.split('/'); + var error_connecting = true; + + var lookup_table = {}; + do { lookup_table[lookup.join('/')]={} } while(lookup.pop()) + for (r in lookup_table){ + try_next(r); + } + + function try_next(table_row){ + var client = net.connect( table_row ); + client.path = table_row + client.on('error', function(){ lookup_table[this.path].error_connecting=true; this.end(); }); + client.on('connect', function(){ lookup_table[this.path].error_connecting=false; this.end(); }); + table_row.client = client; + } + + wait_for_socket_response(); + + response_counter = 0; + + function wait_for_socket_response(){ + var detach; + if('undefined' == typeof setImmediate ) detach = process.nextTick + else detach = setImmediate; + detach(function(){ + // counter to prevent infinite blocking waiting for an open socket to be found. + response_counter++; + var trying = false; + for (r in lookup_table){ + //console.log(r, lookup_table[r], lookup_table[r].error_connecting) + if('undefined' == typeof lookup_table[r].error_connecting) + trying = true; + } + if(trying && response_counter<1000) + wait_for_socket_response() + else + set_socket_properties(); + }) + } + + function set_socket_properties(){ + var host; + for (r in lookup_table){ + if(lookup_table[r].error_connecting === false){ + host = r + } + } + if(!host){ + self.emit('error', new Error("Failed to connect to any socket in "+full_path)) + } + var path = full_path.replace(host, '') + + self.socketPath = host + self.uri.pathname = path + self.uri.href = path + self.uri.path = path + self.host = '' + self.hostname = '' + delete self.host + delete self.hostname + self._buildRequest(); + } + } + + // Intercept UNIX protocol requests to change properties to match socket + if(/^unix:/.test(self.uri.protocol)){ + self._handleUnixSocketURI(self); + } else { + self._buildRequest(); + } + +} + +// Must call this when following a redirect from https to http or vice versa +// Attempts to keep everything as identical as possible, but update the +// httpModule, Tunneling agent, and/or Forever Agent in use. +Request.prototype._updateProtocol = function () { + var self = this + var protocol = self.uri.protocol + + if (protocol === 'https:') { + // previously was doing http, now doing https + // if it's https, then we might need to tunnel now. + if (self.proxy && self.canTunnel) { + self.tunnel = true + var tunnelFn = self.proxy.protocol === 'http:' + ? tunnel.httpsOverHttp : tunnel.httpsOverHttps + var tunnelOptions = { proxy: { host: self.proxy.hostname + , port: +self.proxy.port + , proxyAuth: self.proxy.auth } + , rejectUnauthorized: self.rejectUnauthorized + , ca: self.ca } + self.agent = tunnelFn(tunnelOptions) + return + } + + self.httpModule = https + switch (self.agentClass) { + case ForeverAgent: + self.agentClass = ForeverAgent.SSL + break + case http.Agent: + self.agentClass = https.Agent + break + default: + // nothing we can do. Just hope for the best. + return + } + + // if there's an agent, we need to get a new one. + if (self.agent) self.agent = self.getAgent() + + } else { + // previously was doing https, now doing http + // stop any tunneling. + if (self.tunnel) self.tunnel = false + self.httpModule = http + switch (self.agentClass) { + case ForeverAgent.SSL: + self.agentClass = ForeverAgent + break + case https.Agent: + self.agentClass = http.Agent + break + default: + // nothing we can do. just hope for the best + return + } + + // if there's an agent, then get a new one. + if (self.agent) { + self.agent = null + self.agent = self.getAgent() + } + } +} + +Request.prototype.getAgent = function () { + var Agent = this.agentClass + var options = {} + if (this.agentOptions) { + for (var i in this.agentOptions) { + options[i] = this.agentOptions[i] + } + } + if (this.ca) options.ca = this.ca + if (this.ciphers) options.ciphers = this.ciphers + if (this.secureProtocol) options.secureProtocol = this.secureProtocol + if (this.secureOptions) options.secureOptions = this.secureOptions + if (typeof this.rejectUnauthorized !== 'undefined') options.rejectUnauthorized = this.rejectUnauthorized + + if (this.cert && this.key) { + options.key = this.key + options.cert = this.cert + } + + var poolKey = '' + + // different types of agents are in different pools + if (Agent !== this.httpModule.Agent) { + poolKey += Agent.name + } + + if (!this.httpModule.globalAgent) { + // node 0.4.x + options.host = this.host + options.port = this.port + if (poolKey) poolKey += ':' + poolKey += this.host + ':' + this.port + } + + // ca option is only relevant if proxy or destination are https + var proxy = this.proxy + if (typeof proxy === 'string') proxy = url.parse(proxy) + var isHttps = (proxy && proxy.protocol === 'https:') || this.uri.protocol === 'https:' + if (isHttps) { + if (options.ca) { + if (poolKey) poolKey += ':' + poolKey += options.ca + } + + if (typeof options.rejectUnauthorized !== 'undefined') { + if (poolKey) poolKey += ':' + poolKey += options.rejectUnauthorized + } + + if (options.cert) + poolKey += options.cert.toString('ascii') + options.key.toString('ascii') + + if (options.ciphers) { + if (poolKey) poolKey += ':' + poolKey += options.ciphers + } + + if (options.secureProtocol) { + if (poolKey) poolKey += ':' + poolKey += options.secureProtocol + } + + if (options.secureOptions) { + if (poolKey) poolKey += ':' + poolKey += options.secureOptions + } + } + + if (this.pool === globalPool && !poolKey && Object.keys(options).length === 0 && this.httpModule.globalAgent) { + // not doing anything special. Use the globalAgent + return this.httpModule.globalAgent + } + + // we're using a stored agent. Make sure it's protocol-specific + poolKey = this.uri.protocol + poolKey + + // already generated an agent for this setting + if (this.pool[poolKey]) return this.pool[poolKey] + + return this.pool[poolKey] = new Agent(options) +} + +Request.prototype.start = function () { + // start() is called once we are ready to send the outgoing HTTP request. + // this is usually called on the first write(), end() or on nextTick() + var self = this + + if (self._aborted) return + + self._started = true + self.method = self.method || 'GET' + self.href = self.uri.href + + if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) { + self.setHeader('content-length', self.src.stat.size) + } + if (self._aws) { + self.aws(self._aws, true) + } + + // We have a method named auth, which is completely different from the http.request + // auth option. If we don't remove it, we're gonna have a bad time. + var reqOptions = copy(self) + delete reqOptions.auth + + debug('make request', self.uri.href) + self.req = self.httpModule.request(reqOptions, self.onResponse.bind(self)) + + if (self.timeout && !self.timeoutTimer) { + self.timeoutTimer = setTimeout(function () { + self.req.abort() + var e = new Error("ETIMEDOUT") + e.code = "ETIMEDOUT" + self.emit("error", e) + }, self.timeout) + + // Set additional timeout on socket - in case if remote + // server freeze after sending headers + if (self.req.setTimeout) { // only works on node 0.6+ + self.req.setTimeout(self.timeout, function () { + if (self.req) { + self.req.abort() + var e = new Error("ESOCKETTIMEDOUT") + e.code = "ESOCKETTIMEDOUT" + self.emit("error", e) + } + }) + } + } + + self.req.on('error', self.clientErrorHandler) + self.req.on('drain', function() { + self.emit('drain') + }) + self.on('end', function() { + if ( self.req.connection ) self.req.connection.removeListener('error', self._parserErrorHandler) + }) + self.emit('request', self.req) +} +Request.prototype.onResponse = function (response) { + var self = this + debug('onResponse', self.uri.href, response.statusCode, response.headers) + response.on('end', function() { + debug('response end', self.uri.href, response.statusCode, response.headers) + }); + + // The check on response.connection is a workaround for browserify. + if (response.connection && response.connection.listeners('error').indexOf(self._parserErrorHandler) === -1) { + response.connection.setMaxListeners(0) + response.connection.once('error', self._parserErrorHandler) + } + if (self._aborted) { + debug('aborted', self.uri.href) + response.resume() + return + } + if (self._paused) response.pause() + // Check that response.resume is defined. Workaround for browserify. + else response.resume && response.resume() + + self.response = response + response.request = self + response.toJSON = toJSON + + // XXX This is different on 0.10, because SSL is strict by default + if (self.httpModule === https && + self.strictSSL && + !response.client.authorized) { + debug('strict ssl error', self.uri.href) + var sslErr = response.client.authorizationError + self.emit('error', new Error('SSL Error: '+ sslErr)) + return + } + + if (self.setHost && self.hasHeader('host')) delete self.headers[self.hasHeader('host')] + if (self.timeout && self.timeoutTimer) { + clearTimeout(self.timeoutTimer) + self.timeoutTimer = null + } + + var targetCookieJar = (self._jar && self._jar.setCookie)?self._jar:globalCookieJar; + var addCookie = function (cookie) { + //set the cookie if it's domain in the href's domain. + try { + targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true}); + } catch (e) { + self.emit('error', e); + } + } + + if (hasHeader('set-cookie', response.headers) && (!self._disableCookies)) { + var headerName = hasHeader('set-cookie', response.headers) + if (Array.isArray(response.headers[headerName])) response.headers[headerName].forEach(addCookie) + else addCookie(response.headers[headerName]) + } + + var redirectTo = null + if (response.statusCode >= 300 && response.statusCode < 400 && hasHeader('location', response.headers)) { + var location = response.headers[hasHeader('location', response.headers)] + debug('redirect', location) + + if (self.followAllRedirects) { + redirectTo = location + } else if (self.followRedirect) { + switch (self.method) { + case 'PATCH': + case 'PUT': + case 'POST': + case 'DELETE': + // Do not follow redirects + break + default: + redirectTo = location + break + } + } + } else if (response.statusCode == 401 && self._hasAuth && !self._sentAuth) { + var authHeader = response.headers[hasHeader('www-authenticate', response.headers)] + var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase() + debug('reauth', authVerb) + + switch (authVerb) { + case 'basic': + self.auth(self._user, self._pass, true) + redirectTo = self.uri + break + + case 'bearer': + self.auth(null, null, true, self._bearer) + redirectTo = self.uri + break + + case 'digest': + // TODO: More complete implementation of RFC 2617. + // - check challenge.algorithm + // - support algorithm="MD5-sess" + // - handle challenge.domain + // - support qop="auth-int" only + // - handle Authentication-Info (not necessarily?) + // - check challenge.stale (not necessarily?) + // - increase nc (not necessarily?) + // For reference: + // http://tools.ietf.org/html/rfc2617#section-3 + // https://github.com/bagder/curl/blob/master/lib/http_digest.c + + var challenge = {} + var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi + for (;;) { + var match = re.exec(authHeader) + if (!match) break + challenge[match[1]] = match[2] || match[3]; + } + + var ha1 = md5(self._user + ':' + challenge.realm + ':' + self._pass) + var ha2 = md5(self.method + ':' + self.uri.path) + var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth' + var nc = qop && '00000001' + var cnonce = qop && uuid().replace(/-/g, '') + var digestResponse = qop ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2) : md5(ha1 + ':' + challenge.nonce + ':' + ha2) + var authValues = { + username: self._user, + realm: challenge.realm, + nonce: challenge.nonce, + uri: self.uri.path, + qop: qop, + response: digestResponse, + nc: nc, + cnonce: cnonce, + algorithm: challenge.algorithm, + opaque: challenge.opaque + } + + authHeader = [] + for (var k in authValues) { + if (!authValues[k]) { + //ignore + } else if (k === 'qop' || k === 'nc' || k === 'algorithm') { + authHeader.push(k + '=' + authValues[k]) + } else { + authHeader.push(k + '="' + authValues[k] + '"') + } + } + authHeader = 'Digest ' + authHeader.join(', ') + self.setHeader('authorization', authHeader) + self._sentAuth = true + + redirectTo = self.uri + break + } + } + + if (redirectTo) { + debug('redirect to', redirectTo) + + // ignore any potential response body. it cannot possibly be useful + // to us at this point. + if (self._paused) response.resume() + + if (self._redirectsFollowed >= self.maxRedirects) { + self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop "+self.uri.href)) + return + } + self._redirectsFollowed += 1 + + if (!isUrl.test(redirectTo)) { + redirectTo = url.resolve(self.uri.href, redirectTo) + } + + var uriPrev = self.uri + self.uri = url.parse(redirectTo) + + // handle the case where we change protocol from https to http or vice versa + if (self.uri.protocol !== uriPrev.protocol) { + self._updateProtocol() + } + + self.redirects.push( + { statusCode : response.statusCode + , redirectUri: redirectTo + } + ) + if (self.followAllRedirects && response.statusCode != 401 && response.statusCode != 307) self.method = 'GET' + // self.method = 'GET' // Force all redirects to use GET || commented out fixes #215 + delete self.src + delete self.req + delete self.agent + delete self._started + if (response.statusCode != 401 && response.statusCode != 307) { + // Remove parameters from the previous response, unless this is the second request + // for a server that requires digest authentication. + delete self.body + delete self._form + if (self.headers) { + if (self.hasHeader('host')) delete self.headers[self.hasHeader('host')] + if (self.hasHeader('content-type')) delete self.headers[self.hasHeader('content-type')] + if (self.hasHeader('content-length')) delete self.headers[self.hasHeader('content-length')] + } + } + + self.emit('redirect'); + + self.init() + return // Ignore the rest of the response + } else { + self._redirectsFollowed = self._redirectsFollowed || 0 + // Be a good stream and emit end when the response is finished. + // Hack to emit end on close because of a core bug that never fires end + response.on('close', function () { + if (!self._ended) self.response.emit('end') + }) + + var dataStream + if (self.gzip) { + var contentEncoding = response.headers["content-encoding"] || "identity" + contentEncoding = contentEncoding.trim().toLowerCase() + + if (contentEncoding === "gzip") { + dataStream = zlib.createGunzip() + response.pipe(dataStream) + } else { + // Since previous versions didn't check for Content-Encoding header, + // ignore any invalid values to preserve backwards-compatibility + if (contentEncoding !== "identity") { + debug("ignoring unrecognized Content-Encoding " + contentEncoding) + } + dataStream = response + } + } else { + dataStream = response + } + + if (self.encoding) { + if (self.dests.length !== 0) { + console.error("Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.") + } else if (dataStream.setEncoding) { + dataStream.setEncoding(self.encoding) + } else { + // Should only occur on node pre-v0.9.4 (joyent/node@9b5abe5) with + // zlib streams. + // If/When support for 0.9.4 is dropped, this should be unnecessary. + dataStream = dataStream.pipe(stringstream(self.encoding)) + } + } + + self.emit('response', response) + + self.dests.forEach(function (dest) { + self.pipeDest(dest) + }) + + dataStream.on("data", function (chunk) { + var emitted = self.emit("data", chunk) + if (emitted) { + self._destdata = true + } else { + // pause URL stream until we pipe it + dataStream.pause() + dataStream.unshift(chunk) + } + }) + dataStream.on("end", function (chunk) { + self._ended = true + self.emit("end", chunk) + }) + dataStream.on("close", function () {self.emit("close")}) + + if (self.callback) { + var buffer = [] + var bodyLen = 0 + self.on("data", function (chunk) { + buffer.push(chunk) + bodyLen += chunk.length + }) + self.on("end", function () { + debug('end event', self.uri.href) + if (self._aborted) { + debug('aborted', self.uri.href) + return + } + + if (buffer.length && Buffer.isBuffer(buffer[0])) { + debug('has body', self.uri.href, bodyLen) + var body = new Buffer(bodyLen) + var i = 0 + buffer.forEach(function (chunk) { + chunk.copy(body, i, 0, chunk.length) + i += chunk.length + }) + if (self.encoding === null) { + response.body = body + } else { + response.body = body.toString(self.encoding) + } + } else if (buffer.length) { + // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation. + // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse(). + if (self.encoding === 'utf8' && buffer[0].length > 0 && buffer[0][0] === "\uFEFF") { + buffer[0] = buffer[0].substring(1) + } + response.body = buffer.join('') + } + + if (self._json) { + try { + response.body = JSON.parse(response.body) + } catch (e) {} + } + debug('emitting complete', self.uri.href) + if(response.body == undefined && !self._json) { + response.body = ""; + } + self.emit('complete', response, response.body) + }) + } + //if no callback + else{ + self.on("end", function () { + if (self._aborted) { + debug('aborted', self.uri.href) + return + } + self.emit('complete', response); + }); + } + } + debug('finish init function', self.uri.href) +} + +Request.prototype.abort = function () { + this._aborted = true + + if (this.req) { + this.req.abort() + } + else if (this.response) { + this.response.abort() + } + + this.emit("abort") +} + +Request.prototype.pipeDest = function (dest) { + var response = this.response + // Called after the response is received + if (dest.headers && !dest.headersSent) { + if (hasHeader('content-type', response.headers)) { + var ctname = hasHeader('content-type', response.headers) + if (dest.setHeader) dest.setHeader(ctname, response.headers[ctname]) + else dest.headers[ctname] = response.headers[ctname] + } + + if (hasHeader('content-length', response.headers)) { + var clname = hasHeader('content-length', response.headers) + if (dest.setHeader) dest.setHeader(clname, response.headers[clname]) + else dest.headers[clname] = response.headers[clname] + } + } + if (dest.setHeader && !dest.headersSent) { + for (var i in response.headers) { + // If the response content is being decoded, the Content-Encoding header + // of the response doesn't represent the piped content, so don't pass it. + if (!this.gzip || i !== 'content-encoding') { + dest.setHeader(i, response.headers[i]) + } + } + dest.statusCode = response.statusCode + } + if (this.pipefilter) this.pipefilter(response, dest) +} + +// Composable API +Request.prototype.setHeader = function (name, value, clobber) { + if (clobber === undefined) clobber = true + if (clobber || !this.hasHeader(name)) this.headers[name] = value + else this.headers[this.hasHeader(name)] += ',' + value + return this +} +Request.prototype.setHeaders = function (headers) { + for (var i in headers) {this.setHeader(i, headers[i])} + return this +} +Request.prototype.hasHeader = function (header, headers) { + var headers = Object.keys(headers || this.headers) + , lheaders = headers.map(function (h) {return h.toLowerCase()}) + ; + header = header.toLowerCase() + for (var i=0;iOh hi.' + + '\r\n--__BOUNDARY__\r\n\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + , testPutMultipartPreambleCRLF : + { resp: server.createPostValidator( + '\r\n--__BOUNDARY__\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__\r\n\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__--' + ) + , method: "PUT" + , preambleCRLF: true + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + test.uri = s.url + '/' + i + request(test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + console.log(Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) + diff --git a/packages/wekan-request/tests/test-cookies.js b/packages/wekan-request/tests/test-cookies.js new file mode 100644 index 000000000..5f65d1083 --- /dev/null +++ b/packages/wekan-request/tests/test-cookies.js @@ -0,0 +1,66 @@ +try { + require('tough-cookie') +} catch (e) { + console.error('tough-cookie must be installed to run this test.') + console.error('skipping this test. please install tough-cookie and run again if you need to test this feature.') + process.exit(0) +} + +var assert = require('assert') + , http = require('http') + , request = require('../index') + + +function simpleCookieCreationTest() { + var cookie = request.cookie('foo=bar') + assert(cookie.key === 'foo') + assert(cookie.value === 'bar') +} + +simpleCookieCreationTest() + +var requests = 0; +var validUrl = 'http://localhost:6767/valid'; +var invalidUrl = 'http://localhost:6767/invalid'; + +var server = http.createServer(function (req, res) { + requests++; + if (req.url === '/valid') + res.setHeader('set-cookie', 'foo=bar'); + else if (req.url === '/invalid') + res.setHeader('set-cookie', 'foo=bar; Domain=foo.com'); + res.end('okay'); + if (requests === 2) server.close(); +}); +server.listen(6767); + +var jar1 = request.jar(); +request({ + method: 'GET', + url: validUrl, + jar: jar1 +}, +function (error, response, body) { + if (error) throw error; + assert.equal(jar1.getCookieString(validUrl), 'foo=bar'); + assert.equal(body, 'okay'); + + var cookies = jar1.getCookies(validUrl); + assert(cookies.length == 1); + assert(cookies[0].key === 'foo'); + assert(cookies[0].value === 'bar'); +}); + +var jar2 = request.jar(); +request({ + method: 'GET', + url: invalidUrl, + jar: jar2 +}, +function (error, response, body) { + if (error) throw error; + assert.equal(jar2.getCookieString(validUrl), ''); + assert.deepEqual(jar2.getCookies(validUrl), []); + assert.equal(body, 'okay'); +}); + diff --git a/packages/wekan-request/tests/test-defaults.js b/packages/wekan-request/tests/test-defaults.js new file mode 100644 index 000000000..a6c57f7a8 --- /dev/null +++ b/packages/wekan-request/tests/test-defaults.js @@ -0,0 +1,146 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../index') + ; + +var s = server.createServer(); + +s.listen(s.port, function () { + var counter = 0; + s.on('/get', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.method, 'GET') + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.end('TESTING!'); + }); + + // test get(string, function) + request.defaults({headers:{foo:"bar"}})(s.url + '/get', function (e, r, b){ + if (e) throw e; + assert.deepEqual("TESTING!", b); + counter += 1; + }); + + s.on('/merge-headers', function (req, resp) { + assert.equal(req.headers.foo, 'bar') + assert.equal(req.headers.merged, 'yes') + resp.writeHead(200) + resp.end() + }); + + request.defaults({ + headers:{foo:"bar", merged:"no"} + })(s.url + '/merge-headers', { + headers:{merged:"yes"} + }, function (e, r, b){ + if (e) throw e + assert.equal(r.statusCode, 200) + counter += 1 + }); + + s.on('/post', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.headers['content-type'], null); + assert.equal(req.method, 'POST') + resp.writeHead(200, {'Content-Type': 'application/json'}); + resp.end(JSON.stringify({foo:'bar'})); + }); + + // test post(string, object, function) + request.defaults({headers:{foo:"bar"}}).post(s.url + '/post', {json: true}, function (e, r, b){ + if (e) throw e; + assert.deepEqual('bar', b.foo); + counter += 1; + }); + + s.on('/patch', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.headers['content-type'], null); + assert.equal(req.method, 'PATCH') + resp.writeHead(200, {'Content-Type': 'application/json'}); + resp.end(JSON.stringify({foo:'bar'})); + }); + + // test post(string, object, function) + request.defaults({headers:{foo:"bar"}}).patch(s.url + '/patch', {json: true}, function (e, r, b){ + if (e) throw e; + assert.deepEqual('bar', b.foo); + counter += 1; + }); + + s.on('/post-body', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.headers['content-type'], 'application/json'); + assert.equal(req.method, 'POST') + resp.writeHead(200, {'Content-Type': 'application/json'}); + resp.end(JSON.stringify({foo:'bar'})); + }); + + // test post(string, object, function) with body + request.defaults({headers:{foo:"bar"}}).post(s.url + '/post-body', {json: true, body:{bar:"baz"}}, function (e, r, b){ + if (e) throw e; + assert.deepEqual('bar', b.foo); + counter += 1; + }); + + s.on('/del', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.method, 'DELETE') + resp.writeHead(200, {'Content-Type': 'application/json'}); + resp.end(JSON.stringify({foo:'bar'})); + }); + + // test .del(string, function) + request.defaults({headers:{foo:"bar"}, json:true}).del(s.url + '/del', function (e, r, b){ + if (e) throw e; + assert.deepEqual('bar', b.foo); + counter += 1; + }); + + s.on('/head', function (req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.method, 'HEAD') + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.end(); + }); + + // test head.(object, function) + request.defaults({headers:{foo:"bar"}}).head({uri: s.url + '/head'}, function (e, r, b){ + if (e) throw e; + counter += 1; + }); + + s.on('/get_custom', function(req, resp) { + assert.equal(req.headers.foo, 'bar'); + assert.equal(req.headers.x, 'y'); + resp.writeHead(200, {'Content-Type': 'text/plain'}); + resp.end(); + }); + + // test custom request handler function + var defaultRequest = request.defaults({ + headers:{foo:"bar"} + , body: 'TESTING!' + }, function(uri, options, callback) { + var params = request.initParams(uri, options, callback); + options = params.options; + options.headers.x = 'y'; + + return request(params.uri, params.options, params.callback); + }); + + var msg = 'defaults test failed. head request should throw earlier'; + assert.throws(function() { + defaultRequest.head(s.url + '/get_custom', function(e, r, b) { + throw new Error(msg); + }); + counter+=1; + }, msg); + + defaultRequest.get(s.url + '/get_custom', function(e, r, b) { + if(e) throw e; + counter += 1; + console.log(counter.toString() + " tests passed."); + s.close(); + }); +}) diff --git a/packages/wekan-request/tests/test-digest-auth.js b/packages/wekan-request/tests/test-digest-auth.js new file mode 100644 index 000000000..c99829340 --- /dev/null +++ b/packages/wekan-request/tests/test-digest-auth.js @@ -0,0 +1,99 @@ +var assert = require('assert') + , http = require('http') + , request = require('../index') + ; + +// Test digest auth +// Using header values captured from interaction with Apache + +var numDigestRequests = 0; + +var digestServer = http.createServer(function (req, res) { + console.error('Digest auth server: ', req.method, req.url); + numDigestRequests++; + + var ok; + + if (req.url === '/test/') { + if (req.headers.authorization) { + if (/^Digest username="test", realm="Private", nonce="WpcHS2\/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93", uri="\/test\/", qop=auth, response="[a-f0-9]{32}", nc=00000001, cnonce="[a-f0-9]{32}", algorithm=MD5, opaque="5ccc069c403ebaf9f0171e9517f40e41"$/.exec(req.headers.authorization)) { + ok = true; + } else { + // Bad auth header, don't send back WWW-Authenticate header + ok = false; + } + } else { + // No auth header, send back WWW-Authenticate header + ok = false; + res.setHeader('www-authenticate', 'Digest realm="Private", nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93", algorithm=MD5, qop="auth", opaque="5ccc069c403ebaf9f0171e9517f40e41"'); + } + } else if (req.url === '/dir/index.html') { + // RFC2069-compatible mode + // check: http://www.rfc-editor.org/errata_search.php?rfc=2069 + if (req.headers.authorization) { + if (/^Digest username="Mufasa", realm="testrealm@host.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="\/dir\/index.html", response="[a-f0-9]{32}", opaque="5ccc069c403ebaf9f0171e9517f40e41"$/.exec(req.headers.authorization)) { + ok = true; + } else { + // Bad auth header, don't send back WWW-Authenticate header + ok = false; + } + } else { + // No auth header, send back WWW-Authenticate header + ok = false; + res.setHeader('www-authenticate', 'Digest realm="testrealm@host.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"'); + } + } + + if (ok) { + console.log('request ok'); + res.end('ok'); + } else { + console.log('status=401'); + res.statusCode = 401; + res.end('401'); + } +}); + +digestServer.listen(6767); + +request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test/', + 'auth': { + 'user': 'test', + 'pass': 'testing', + 'sendImmediately': false + } +}, function(error, response, body) { + assert.equal(response.statusCode, 200); + assert.equal(numDigestRequests, 2); + + // If we don't set sendImmediately = false, request will send basic auth + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/test/', + 'auth': { + 'user': 'test', + 'pass': 'testing' + } + }, function(error, response, body) { + assert.equal(response.statusCode, 401); + assert.equal(numDigestRequests, 3); + + request({ + 'method': 'GET', + 'uri': 'http://localhost:6767/dir/index.html', + 'auth': { + 'user': 'Mufasa', + 'pass': 'CircleOfLife', + 'sendImmediately': false + } + }, function(error, response, body) { + assert.equal(response.statusCode, 200); + assert.equal(numDigestRequests, 5); + + console.log('All tests passed'); + digestServer.close(); + }); + }); +}); diff --git a/packages/wekan-request/tests/test-emptyBody.js b/packages/wekan-request/tests/test-emptyBody.js new file mode 100644 index 000000000..338c92e5e --- /dev/null +++ b/packages/wekan-request/tests/test-emptyBody.js @@ -0,0 +1,20 @@ +var request = require('../index') + , http = require('http') + , assert = require('assert') + ; + +var s = http.createServer(function (req, resp) { + resp.statusCode = 200 + resp.end('') +}).listen(8080, function () { + var r = request('http://localhost:8080', function (e, resp, body) { + assert.equal(resp.statusCode, 200) + assert.equal(body, "") + + var r2 = request({ url: 'http://localhost:8080', json: {} }, function (e, resp, body) { + assert.equal(resp.statusCode, 200) + assert.equal(body, undefined) + s.close() + }); + }) +}) diff --git a/packages/wekan-request/tests/test-errors.js b/packages/wekan-request/tests/test-errors.js new file mode 100644 index 000000000..4df1302a0 --- /dev/null +++ b/packages/wekan-request/tests/test-errors.js @@ -0,0 +1,37 @@ +var server = require('./server') + , events = require('events') + , assert = require('assert') + , request = require('../index') + ; + +var local = 'http://localhost:8888/asdf' + +try { + request({uri:local, body:{}}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Argument error, options.body.') +} + +try { + request({uri:local, multipart: 'foo'}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Argument error, options.multipart.') +} + +try { + request({uri:local, multipart: [{}]}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Body attribute missing in multipart.') +} + +try { + request(local, {multipart: [{}]}) + assert.fail("Should have throw") +} catch(e) { + assert.equal(e.message, 'Body attribute missing in multipart.') +} + +console.log("All tests passed.") diff --git a/packages/wekan-request/tests/test-follow-all-303.js b/packages/wekan-request/tests/test-follow-all-303.js new file mode 100644 index 000000000..956e386d1 --- /dev/null +++ b/packages/wekan-request/tests/test-follow-all-303.js @@ -0,0 +1,30 @@ +var request = require('../index'); +var http = require('http'); +var requests = 0; +var assert = require('assert'); + +var server = http.createServer(function (req, res) { + console.error(req.method, req.url); + requests ++; + + if (req.method === 'POST') { + console.error('send 303'); + res.setHeader('location', req.url); + res.statusCode = 303; + res.end('try again, i guess\n'); + } else { + console.error('send 200') + res.end('ok: ' + requests); + } +}); +server.listen(6767); + +request.post({ url: 'http://localhost:6767/foo', + followAllRedirects: true, + form: { foo: 'bar' } }, function (er, req, body) { + if (er) throw er; + assert.equal(body, 'ok: 2'); + assert.equal(requests, 2); + console.error('ok - ' + process.version); + server.close(); +}); diff --git a/packages/wekan-request/tests/test-follow-all.js b/packages/wekan-request/tests/test-follow-all.js new file mode 100644 index 000000000..1580677ac --- /dev/null +++ b/packages/wekan-request/tests/test-follow-all.js @@ -0,0 +1,44 @@ +try { + require('tough-cookie') +} catch (e) { + console.error('tough-cookie must be installed to run this test.') + console.error('skipping this test. please install tough-cookie and run again if you need to test this feature.') + process.exit(0) +} + +var request = require('../index'); +var http = require('http'); +var requests = 0; +var assert = require('assert'); + +var server = http.createServer(function (req, res) { + requests ++; + + // redirect everything 3 times, no matter what. + var c = req.headers.cookie; + + if (!c) c = 0; + else c = +c.split('=')[1] || 0; + + if (c > 3) { + res.end('ok: '+requests); + return; + } + + res.setHeader('set-cookie', 'c=' + (c + 1)); + res.setHeader('location', req.url); + res.statusCode = 302; + res.end('try again, i guess\n'); +}); +server.listen(6767); + +request.post({ url: 'http://localhost:6767/foo', + followAllRedirects: true, + jar: true, + form: { foo: 'bar' } }, function (er, req, body) { + if (er) throw er; + assert.equal(body, 'ok: 5'); + assert.equal(requests, 5); + console.error('ok - ' + process.version); + server.close(); +}); diff --git a/packages/wekan-request/tests/test-form.js b/packages/wekan-request/tests/test-form.js new file mode 100644 index 000000000..f229e61a4 --- /dev/null +++ b/packages/wekan-request/tests/test-form.js @@ -0,0 +1,96 @@ +try { + require('form-data') +} catch (e) { + console.error('form-data must be installed to run this test.') + console.error('skipping this test. please install form-data and run again if you need to test this feature.') + process.exit(0) +} + +var assert = require('assert') +var http = require('http'); +var path = require('path'); +var mime = require('mime-types'); +var request = require('../index'); +var fs = require('fs'); + +var remoteFile = 'http://nodejs.org/images/logo.png'; + +var totalLength = null; + +var FIELDS = [ + {name: 'my_field', value: 'my_value'}, + {name: 'my_buffer', value: new Buffer([1, 2, 3])}, + {name: 'my_file', value: fs.createReadStream(__dirname + '/unicycle.jpg')}, + {name: 'remote_file', value: request(remoteFile) } +]; + +var server = http.createServer(function(req, res) { + + // temp workaround + var data = ''; + req.setEncoding('utf8'); + + req.on('data', function(d) { + data += d; + }); + + req.on('end', function() { + // check for the fields' traces + + // 1st field : my_field + var field = FIELDS.shift(); + assert.ok( data.indexOf('form-data; name="'+field.name+'"') != -1 ); + assert.ok( data.indexOf(field.value) != -1 ); + + // 2nd field : my_buffer + var field = FIELDS.shift(); + assert.ok( data.indexOf('form-data; name="'+field.name+'"') != -1 ); + assert.ok( data.indexOf(field.value) != -1 ); + + // 3rd field : my_file + var field = FIELDS.shift(); + assert.ok( data.indexOf('form-data; name="'+field.name+'"') != -1 ); + assert.ok( data.indexOf('; filename="'+path.basename(field.value.path)+'"') != -1 ); + // check for unicycle.jpg traces + assert.ok( data.indexOf('2005:06:21 01:44:12') != -1 ); + assert.ok( data.indexOf('Content-Type: '+mime.lookup(field.value.path) ) != -1 ); + + // 4th field : remote_file + var field = FIELDS.shift(); + assert.ok( data.indexOf('form-data; name="'+field.name+'"') != -1 ); + assert.ok( data.indexOf('; filename="'+path.basename(field.value.path)+'"') != -1 ); + // check for http://nodejs.org/images/logo.png traces + assert.ok( data.indexOf('ImageReady') != -1 ); + assert.ok( data.indexOf('Content-Type: '+mime.lookup(remoteFile) ) != -1 ); + + assert.ok( req.headers['content-length'] == totalLength ); + + + res.writeHead(200); + res.end('done'); + + }); + + +}); + +server.listen(8080, function() { + + var req = request.post('http://localhost:8080/upload', function () { + server.close(); + }) + var form = req.form() + + FIELDS.forEach(function(field) { + form.append(field.name, field.value); + }); + + form.getLength(function (err, length) { + totalLength = length; + }); + +}); + +process.on('exit', function() { + assert.strictEqual(FIELDS.length, 0); +}); diff --git a/packages/wekan-request/tests/test-gzip.js b/packages/wekan-request/tests/test-gzip.js new file mode 100644 index 000000000..b52826ee5 --- /dev/null +++ b/packages/wekan-request/tests/test-gzip.js @@ -0,0 +1,105 @@ +var request = require('../index') + , http = require('http') + , assert = require('assert') + , zlib = require('zlib') + +if (!zlib.Gunzip.prototype.setEncoding) { + try { + require('stringstream') + } catch (e) { + console.error('stringstream must be installed to run this test.') + console.error('skipping this test. please install stringstream and run again if you need to test this feature.') + process.exit(0) + } +} + +var testContent = 'Compressible response content.\n' + , testContentGzip + +var server = http.createServer(function (req, res) { + res.statusCode = 200 + res.setHeader('Content-Type', 'text/plain') + + if (/\bgzip\b/i.test(req.headers['accept-encoding'])) { + zlib.gzip(testContent, function (err, data) { + assert.ifError(err) + testContentGzip = data + res.setHeader('Content-Encoding', 'gzip') + res.end(data) + }) + } else { + res.end(testContent) + } +}) + +server.listen(6767, function (err) { + assert.ifError(err) + + var headers, options + + // Transparently supports gzip decoding to callbacks + options = { url: 'http://localhost:6767/foo', gzip: true } + request.get(options, function (err, res, body) { + assert.ifError(err) + assert.strictEqual(res.headers['content-encoding'], 'gzip') + assert.strictEqual(body, testContent) + }) + + + // Transparently supports gzip decoding to pipes + options = { url: 'http://localhost:6767/foo', gzip: true } + var chunks = [] + request.get(options) + .on('data', function (chunk) { chunks.push(chunk) }) + .on('end', function () { + assert.strictEqual(Buffer.concat(chunks).toString(), testContent) + }) + .on('error', function (err) { assert.ifError(err) }) + + + // Does not request gzip if user specifies Accepted-Encodings + headers = { 'Accept-Encoding': null } + options = { + url: 'http://localhost:6767/foo', + headers: headers, + gzip: true + } + request.get(options, function (err, res, body) { + assert.ifError(err) + assert.strictEqual(res.headers['content-encoding'], undefined) + assert.strictEqual(body, testContent) + }) + + + // Does not decode user-requested encoding by default + headers = { 'Accept-Encoding': 'gzip' } + options = { url: 'http://localhost:6767/foo', headers: headers } + request.get(options, function (err, res, body) { + assert.ifError(err) + assert.strictEqual(res.headers['content-encoding'], 'gzip') + assert.strictEqual(body, testContentGzip.toString()) + }) + + + // Supports character encoding with gzip encoding + headers = { 'Accept-Encoding': 'gzip' } + options = { + url: 'http://localhost:6767/foo', + headers: headers, + gzip: true, + encoding: "utf8" + } + var strings = [] + request.get(options) + .on('data', function (string) { + assert.strictEqual(typeof string, "string") + strings.push(string) + }) + .on('end', function () { + assert.strictEqual(strings.join(""), testContent) + + // Shutdown server after last test + server.close() + }) + .on('error', function (err) { assert.ifError(err) }) +}) diff --git a/packages/wekan-request/tests/test-hawk.js b/packages/wekan-request/tests/test-hawk.js new file mode 100755 index 000000000..2382f16e3 --- /dev/null +++ b/packages/wekan-request/tests/test-hawk.js @@ -0,0 +1,41 @@ +try { + require('hawk') +} catch (e) { + console.error('hawk must be installed to run this test.') + console.error('skipping this test. please install hawk and run again if you need to test this feature.') + process.exit(0) +} + +var createServer = require('http').createServer + , request = require('../index') + , hawk = require('hawk') + , assert = require('assert') + ; + +var server = createServer(function (req, resp) { + + var getCred = function (id, callback) { + assert.equal(id, 'dh37fgj492je') + var credentials = + { key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn' + , algorithm: 'sha256' + , user: 'Steve' + } + return callback(null, credentials) + } + + hawk.server.authenticate(req, getCred, {}, function (err, credentials, attributes) { + resp.writeHead(!err ? 200 : 401, { 'Content-Type': 'text/plain' }) + resp.end(!err ? 'Hello ' + credentials.user : 'Shoosh!') + }) + +}) + +server.listen(8080, function () { + var creds = {key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', algorithm: 'sha256', id:'dh37fgj492je'} + request('http://localhost:8080', {hawk:{credentials:creds}}, function (e, r, b) { + assert.equal(200, r.statusCode) + assert.equal(b, 'Hello Steve') + server.close() + }) +}) \ No newline at end of file diff --git a/packages/wekan-request/tests/test-headers.js b/packages/wekan-request/tests/test-headers.js new file mode 100644 index 000000000..0b0562201 --- /dev/null +++ b/packages/wekan-request/tests/test-headers.js @@ -0,0 +1,79 @@ +try { + require('tough-cookie') +} catch (e) { + console.error('tough-cookie must be installed to run this test.') + console.error('skipping this test. please install tough-cookie and run again if you need to test this feature.') + process.exit(0) +} + +var server = require('./server') + , assert = require('assert') + , request = require('../index') + , s = server.createServer() + +s.listen(s.port, function () { + var serverUri = 'http://localhost:' + s.port + , numTests = 0 + , numOutstandingTests = 0 + + function createTest(requestObj, serverAssertFn) { + var testNumber = numTests; + numTests += 1; + numOutstandingTests += 1; + s.on('/' + testNumber, function (req, res) { + serverAssertFn(req, res); + res.writeHead(200); + res.end(); + }); + requestObj.url = serverUri + '/' + testNumber + request(requestObj, function (err, res, body) { + assert.ok(!err) + assert.equal(res.statusCode, 200) + numOutstandingTests -= 1 + if (numOutstandingTests === 0) { + console.log(numTests + ' tests passed.') + s.close() + } + }) + } + + // Issue #125: headers.cookie shouldn't be replaced when a cookie jar isn't specified + createTest({headers: {cookie: 'foo=bar'}}, function (req, res) { + assert.ok(req.headers.cookie) + assert.equal(req.headers.cookie, 'foo=bar') + }) + + // Issue #125: headers.cookie + cookie jar + //using new cookie module + var jar = request.jar() + jar.setCookie('quux=baz', serverUri); + createTest({jar: jar, headers: {cookie: 'foo=bar'}}, function (req, res) { + assert.ok(req.headers.cookie) + assert.equal(req.headers.cookie, 'foo=bar; quux=baz') + }) + + // Issue #794 add ability to ignore cookie parsing and domain errors + var jar2 = request.jar() + jar2.setCookie('quux=baz; Domain=foo.bar.com', serverUri, {ignoreError: true}); + createTest({jar: jar2, headers: {cookie: 'foo=bar'}}, function (req, res) { + assert.ok(req.headers.cookie) + assert.equal(req.headers.cookie, 'foo=bar') + }) + + // Issue #784: override content-type when json is used + // https://github.com/mikeal/request/issues/784 + createTest({ + json: true, + method: 'POST', + headers: {'content-type': 'application/json; charset=UTF-8'}, + body: {hello: 'my friend'}},function(req, res) { + assert.ok(req.headers['content-type']); + assert.equal(req.headers['content-type'], 'application/json; charset=UTF-8'); + } + ) + + // There should be no cookie header when neither headers.cookie nor a cookie jar is specified + createTest({}, function (req, res) { + assert.ok(!req.headers.cookie) + }) +}) diff --git a/packages/wekan-request/tests/test-http-signature.js b/packages/wekan-request/tests/test-http-signature.js new file mode 100755 index 000000000..ee04cfbb4 --- /dev/null +++ b/packages/wekan-request/tests/test-http-signature.js @@ -0,0 +1,114 @@ +try { + require('http-signature') +} catch (e) { + console.error('http-signature must be installed to run this test.') + console.error('skipping this test. please install http-signature and run again if you need to test this feature.') + process.exit(0) +} + +var createServer = require('http').createServer + , request = require('../index') + , httpSignature = require('http-signature') + , assert = require('assert') + ; + +var privateKeyPEMs = {} + +privateKeyPEMs['key-1'] = + '-----BEGIN RSA PRIVATE KEY-----\n' + + 'MIIEpAIBAAKCAQEAzWSJl+Z9Bqv00FVL5N3+JCUoqmQPjIlya1BbeqQroNQ5yG1i\n' + + 'VbYTTnMRa1zQtR6r2fNvWeg94DvxivxIG9diDMnrzijAnYlTLOl84CK2vOxkj5b6\n' + + '8zrLH9b/Gd6NOHsywo8IjvXvCeTfca5WUHcuVi2lT9VjygFs1ILG4RyeX1BXUumu\n' + + 'Y8fzmposxLYdMxCqUTzAn0u9Saq2H2OVj5u114wS7OQPigu6G99dpn/iPHa3zBm8\n' + + '7baBWDbqZWRW0BP3K6eqq8sut1+NLhNW8ADPTdnO/SO+kvXy7fqd8atSn+HlQcx6\n' + + 'tW42dhXf3E9uE7K78eZtW0KvfyNGAjsI1Fft2QIDAQABAoIBAG1exe3/LEBrPLfb\n' + + 'U8iRdY0lxFvHYIhDgIwohC3wUdMYb5SMurpNdEZn+7Sh/fkUVgp/GKJViu1mvh52\n' + + 'bKd2r52DwG9NQBQjVgkqY/auRYSglIPpr8PpYNSZlcneunCDGeqEY9hMmXc5Ssqs\n' + + 'PQYoEKKPN+IlDTg6PguDgAfLR4IUvt9KXVvmB/SSgV9tSeTy35LECt1Lq3ozbUgu\n' + + '30HZI3U6/7H+X22Pxxf8vzBtzkg5rRCLgv+OeNPo16xMnqbutt4TeqEkxRv5rtOo\n' + + '/A1i9khBeki0OJAFJsE82qnaSZodaRsxic59VnN8sWBwEKAt87tEu5A3K3j4XSDU\n' + + '/avZxAECgYEA+pS3DvpiQLtHlaO3nAH6MxHRrREOARXWRDe5nUQuUNpS1xq9wte6\n' + + 'DkFtba0UCvDLic08xvReTCbo9kH0y6zEy3zMpZuJlKbcWCkZf4S5miYPI0RTZtF8\n' + + 'yps6hWqzYFSiO9hMYws9k4OJLxX0x3sLK7iNZ32ujcSrkPBSiBr0gxkCgYEA0dWl\n' + + '637K41AJ/zy0FP0syq+r4eIkfqv+/t6y2aQVUBvxJYrj9ci6XHBqoxpDV8lufVYj\n' + + 'fUAfeI9/MZaWvQJRbnYLre0I6PJfLuCBIL5eflO77BGso165AF7QJZ+fwtgKv3zv\n' + + 'ZX75eudCSS/cFo0po9hlbcLMT4B82zEkgT8E2MECgYEAnz+3/wrdOmpLGiyL2dff\n' + + '3GjsqmJ2VfY8z+niSrI0BSpbD11tT9Ct67VlCBjA7hsOH6uRfpd6/kaUMzzDiFVq\n' + + 'VDAiFvV8QD6zNkwYalQ9aFvbrvwTTPrBpjl0vamMCiJ/YC0cjq1sGr2zh3sar1Ph\n' + + 'S43kP+s97dcZeelhaiJHVrECgYEAsx61q/loJ/LDFeYzs1cLTVn4V7I7hQY9fkOM\n' + + 'WM0AhInVqD6PqdfXfeFYpjJdGisQ7l0BnoGGW9vir+nkcyPvb2PFRIr6+B8tsU5j\n' + + '7BeVgjDoUfQkcrEBK5fEBtnj/ud9BUkY8oMZZBjVNLRuI7IMwZiPvMp0rcj4zAN/\n' + + 'LfUlpgECgYArBvFcBxSkNAzR3Rtteud1YDboSKluRM37Ey5plrn4BS0DD0jm++aD\n' + + '0pG2Hsik000hibw92lCkzvvBVAqF8BuAcnPlAeYfsOaa97PGEjSKEN5bJVWZ9/om\n' + + '9FV1axotRN2XWlwrhixZLEaagkREXhgQc540FS5O8IaI2Vpa80Atzg==\n' + + '-----END RSA PRIVATE KEY-----' + +var publicKeyPEMs = {} + +publicKeyPEMs['key-1'] = + '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzWSJl+Z9Bqv00FVL5N3+\n' + + 'JCUoqmQPjIlya1BbeqQroNQ5yG1iVbYTTnMRa1zQtR6r2fNvWeg94DvxivxIG9di\n' + + 'DMnrzijAnYlTLOl84CK2vOxkj5b68zrLH9b/Gd6NOHsywo8IjvXvCeTfca5WUHcu\n' + + 'Vi2lT9VjygFs1ILG4RyeX1BXUumuY8fzmposxLYdMxCqUTzAn0u9Saq2H2OVj5u1\n' + + '14wS7OQPigu6G99dpn/iPHa3zBm87baBWDbqZWRW0BP3K6eqq8sut1+NLhNW8ADP\n' + + 'TdnO/SO+kvXy7fqd8atSn+HlQcx6tW42dhXf3E9uE7K78eZtW0KvfyNGAjsI1Fft\n' + + '2QIDAQAB\n' + + '-----END PUBLIC KEY-----' + +publicKeyPEMs['key-2'] = + '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqp04VVr9OThli9b35Omz\n' + + 'VqSfWbsoQuRrgyWsrNRn3XkFmbWw4FzZwQ42OgGMzQ84Ta4d9zGKKQyFriTiPjPf\n' + + 'xhhrsaJnDuybcpVhcr7UNKjSZ0S59tU3hpRiEz6hO+Nc/OSSLkvalG0VKrxOln7J\n' + + 'LK/h3rNS/l6wDZ5S/KqsI6CYtV2ZLpn3ahLrizvEYNY038Qcm38qMWx+VJAvZ4di\n' + + 'qqmW7RLIsLT59SWmpXdhFKnkYYGhxrk1Mwl22dBTJNY5SbriU5G3gWgzYkm8pgHr\n' + + '6CtrXch9ciJAcDJehPrKXNvNDOdUh8EW3fekNJerF1lWcwQg44/12v8sDPyfbaKB\n' + + 'dQIDAQAB\n' + + '-----END PUBLIC KEY-----' + +var server = createServer(function (req, res) { + var parsed = httpSignature.parseRequest(req) + var publicKeyPEM = publicKeyPEMs[parsed.keyId] + var verified = httpSignature.verifySignature(parsed, publicKeyPEM) + res.writeHead(verified ? 200 : 400) + res.end() +}) + +server.listen(8080, function () { + function correctKeyTest(callback) { + var options = { + httpSignature: { + keyId: 'key-1', + key: privateKeyPEMs['key-1'] + } + } + request('http://localhost:8080', options, function (e, r, b) { + assert.equal(200, r.statusCode) + callback() + }) + } + + function incorrectKeyTest(callback) { + var options = { + httpSignature: { + keyId: 'key-2', + key: privateKeyPEMs['key-1'] + } + } + request('http://localhost:8080', options, function (e, r, b) { + assert.equal(400, r.statusCode) + callback() + }) + } + + var tests = [correctKeyTest, incorrectKeyTest] + var todo = tests.length; + for(var i = 0; i < tests.length; ++i) { + tests[i](function() { + if(!--todo) { + server.close() + } + }) + } +}) diff --git a/packages/wekan-request/tests/test-httpModule.js b/packages/wekan-request/tests/test-httpModule.js new file mode 100644 index 000000000..2c19615f7 --- /dev/null +++ b/packages/wekan-request/tests/test-httpModule.js @@ -0,0 +1,94 @@ +var http = require('http') + , https = require('https') + , server = require('./server') + , assert = require('assert') + , request = require('../index') + + +var faux_requests_made = {'http':0, 'https':0} +function wrap_request(name, module) { + // Just like the http or https module, but note when a request is made. + var wrapped = {} + Object.keys(module).forEach(function(key) { + var value = module[key]; + + if(key != 'request') + wrapped[key] = value; + else + wrapped[key] = function(options, callback) { + faux_requests_made[name] += 1 + return value.apply(this, arguments) + } + }) + + return wrapped; +} + + +var faux_http = wrap_request('http', http) + , faux_https = wrap_request('https', https) + , plain_server = server.createServer() + , https_server = server.createSSLServer() + + +plain_server.listen(plain_server.port, function() { + plain_server.on('/plain', function (req, res) { + res.writeHead(200) + res.end('plain') + }) + plain_server.on('/to_https', function (req, res) { + res.writeHead(301, {'location':'https://localhost:'+https_server.port + '/https'}) + res.end() + }) + + https_server.listen(https_server.port, function() { + https_server.on('/https', function (req, res) { + res.writeHead(200) + res.end('https') + }) + https_server.on('/to_plain', function (req, res) { + res.writeHead(302, {'location':'http://localhost:'+plain_server.port + '/plain'}) + res.end() + }) + + run_tests() + run_tests({}) + run_tests({'http:':faux_http}) + run_tests({'https:':faux_https}) + run_tests({'http:':faux_http, 'https:':faux_https}) + }) +}) + +function run_tests(httpModules) { + var to_https = 'http://localhost:'+plain_server.port+'/to_https' + var to_plain = 'https://localhost:'+https_server.port+'/to_plain' + + request(to_https, {'httpModules':httpModules, strictSSL:false}, function (er, res, body) { + if (er) throw er + assert.equal(body, 'https', 'Received HTTPS server body') + done() + }) + + request(to_plain, {'httpModules':httpModules, strictSSL:false}, function (er, res, body) { + if (er) throw er + assert.equal(body, 'plain', 'Received HTTPS server body') + done() + }) +} + + +var passed = 0; +function done() { + passed += 1 + var expected = 10 + + if(passed == expected) { + plain_server.close() + https_server.close() + + assert.equal(faux_requests_made.http, 4, 'Wrapped http module called appropriately') + assert.equal(faux_requests_made.https, 4, 'Wrapped https module called appropriately') + + console.log((expected+2) + ' tests passed.') + } +} diff --git a/packages/wekan-request/tests/test-https-strict.js b/packages/wekan-request/tests/test-https-strict.js new file mode 100644 index 000000000..d49a9afcb --- /dev/null +++ b/packages/wekan-request/tests/test-https-strict.js @@ -0,0 +1,97 @@ +// a test where we validate the siguature of the keys +// otherwise exactly the same as the ssl test + +var server = require('./server') + , assert = require('assert') + , request = require('../index') + , fs = require('fs') + , path = require('path') + , opts = { key: path.resolve(__dirname, 'ssl/ca/server.key') + , cert: path.resolve(__dirname, 'ssl/ca/server.crt') } + , s = server.createSSLServer(null, opts) + , caFile = path.resolve(__dirname, 'ssl/ca/ca.crt') + , ca = fs.readFileSync(caFile) + +var tests = + { testGet : + { resp : server.createGetResponse("TESTING!") + , expectBody: "TESTING!" + } + , testGetChunkBreak : + { resp : server.createChunkResponse( + [ new Buffer([239]) + , new Buffer([163]) + , new Buffer([191]) + , new Buffer([206]) + , new Buffer([169]) + , new Buffer([226]) + , new Buffer([152]) + , new Buffer([131]) + ]) + , expectBody: "Ω☃" + } + , testGetJSON : + { resp : server.createGetResponse('{"test":true}', 'application/json') + , json : true + , expectBody: {"test":true} + } + , testPutString : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : "PUTTINGDATA" + } + , testPutBuffer : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : new Buffer("PUTTINGDATA") + } + , testPutJSON : + { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) + , method: "PUT" + , json: {foo: 'bar'} + } + , testPutMultipart : + { resp: server.createPostValidator( + '--__BOUNDARY__\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__\r\n\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + test.uri = s.url + '/' + i + test.strictSSL = true + test.ca = ca + test.headers = { host: 'testing.request.mikealrogers.com' } + request(test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + console.log(Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) diff --git a/packages/wekan-request/tests/test-https.js b/packages/wekan-request/tests/test-https.js new file mode 100644 index 000000000..b6858d433 --- /dev/null +++ b/packages/wekan-request/tests/test-https.js @@ -0,0 +1,87 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../index') + +var s = server.createSSLServer(); + +var tests = + { testGet : + { resp : server.createGetResponse("TESTING!") + , expectBody: "TESTING!" + } + , testGetChunkBreak : + { resp : server.createChunkResponse( + [ new Buffer([239]) + , new Buffer([163]) + , new Buffer([191]) + , new Buffer([206]) + , new Buffer([169]) + , new Buffer([226]) + , new Buffer([152]) + , new Buffer([131]) + ]) + , expectBody: "Ω☃" + } + , testGetJSON : + { resp : server.createGetResponse('{"test":true}', 'application/json') + , json : true + , expectBody: {"test":true} + } + , testPutString : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : "PUTTINGDATA" + } + , testPutBuffer : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : new Buffer("PUTTINGDATA") + } + , testPutJSON : + { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) + , method: "PUT" + , json: {foo: 'bar'} + } + , testPutMultipart : + { resp: server.createPostValidator( + '--__BOUNDARY__\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__\r\n\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + test.uri = s.url + '/' + i + test.rejectUnauthorized = false + request(test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + console.log(Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) diff --git a/packages/wekan-request/tests/test-isUrl.js b/packages/wekan-request/tests/test-isUrl.js new file mode 100644 index 000000000..4a51ad018 --- /dev/null +++ b/packages/wekan-request/tests/test-isUrl.js @@ -0,0 +1,28 @@ +var assert = require('assert') + , request = require('../index') + , http = require('http') + ; + +var s = http.createServer(function(req, res) { + res.statusCode = 200; + res.end(''); +}).listen(6767, function () { + + // Test lowercase + request('http://localhost:6767', function (err, resp, body) { + // just need to get here without throwing an error + assert.equal(true, true); + }) + + // Test uppercase + request('HTTP://localhost:6767', function (err, resp, body) { + assert.equal(true, true); + }) + + // Test mixedcase + request('HtTp://localhost:6767', function (err, resp, body) { + assert.equal(true, true); + // clean up + s.close(); + }) +}) \ No newline at end of file diff --git a/packages/wekan-request/tests/test-localAddress.js b/packages/wekan-request/tests/test-localAddress.js new file mode 100644 index 000000000..11a1bd125 --- /dev/null +++ b/packages/wekan-request/tests/test-localAddress.js @@ -0,0 +1,15 @@ +var request = require('../index') + , assert = require('assert') + ; + +request.get({ + uri: 'http://www.google.com', localAddress: '1.2.3.4' // some invalid address +}, function(err, res) { + assert(!res) // asserting that no response received +}) + +request.get({ + uri: 'http://www.google.com', localAddress: '127.0.0.1' +}, function(err, res) { + assert(!res) // asserting that no response received +}) diff --git a/packages/wekan-request/tests/test-node-debug.js b/packages/wekan-request/tests/test-node-debug.js new file mode 100644 index 000000000..a34253270 --- /dev/null +++ b/packages/wekan-request/tests/test-node-debug.js @@ -0,0 +1,26 @@ +var assert = require('assert') + , request = require('../index') + , http = require('http') + ; + +var s = http.createServer(function(req, res) { + res.statusCode = 200 + res.end('') +}).listen(6767, function () { + // a simple request should not fail with NODE_DEBUG + process.env.NODE_DEBUG = 'mumblemumble,request' + + var stderr = '' + process.stderr.write = (function(write) { + return function(string, encoding, fd) { + stderr += string + } + })(process.stderr.write) + + request('http://localhost:6767', function (err, resp, body) { + assert.ifError(err, 'the request did not fail') + assert.ok(resp, 'the request did not fail') + assert.ok(/REQUEST/.test(stderr), 'stderr has some messages') + s.close(); // clean up + }) +}) diff --git a/packages/wekan-request/tests/test-oauth.js b/packages/wekan-request/tests/test-oauth.js new file mode 100644 index 000000000..e57a761c2 --- /dev/null +++ b/packages/wekan-request/tests/test-oauth.js @@ -0,0 +1,125 @@ +try { + require('oauth-sign') +} catch (e) { + console.error('oauth-sign must be installed to run this test.') + console.error('skipping this test. please install oauth-sign and run again if you need to test this feature.') + process.exit(0) +} + +var hmacsign = require('oauth-sign').hmacsign + , assert = require('assert') + , qs = require('querystring') + , request = require('../index') + ; + +function getsignature (r) { + var sign + r.headers.Authorization.slice('OAuth '.length).replace(/,\ /g, ',').split(',').forEach(function (v) { + if (v.slice(0, 'oauth_signature="'.length) === 'oauth_signature="') sign = v.slice('oauth_signature="'.length, -1) + }) + return decodeURIComponent(sign) +} + +// Tests from Twitter documentation https://dev.twitter.com/docs/auth/oauth + +var reqsign = hmacsign('POST', 'https://api.twitter.com/oauth/request_token', + { oauth_callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' + , oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' + , oauth_nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' + , oauth_signature_method: 'HMAC-SHA1' + , oauth_timestamp: '1272323042' + , oauth_version: '1.0' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98") + +console.log(reqsign) +console.log('8wUi7m5HFQy76nowoCThusfgB+Q=') +assert.equal(reqsign, '8wUi7m5HFQy76nowoCThusfgB+Q=') + +var accsign = hmacsign('POST', 'https://api.twitter.com/oauth/access_token', + { oauth_consumer_key: 'GDdmIQH6jhtmLUypg82g' + , oauth_nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' + , oauth_signature_method: 'HMAC-SHA1' + , oauth_token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' + , oauth_timestamp: '1272323047' + , oauth_verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' + , oauth_version: '1.0' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA") + +console.log(accsign) +console.log('PUw/dHA4fnlJYM6RhXk5IU/0fCc=') +assert.equal(accsign, 'PUw/dHA4fnlJYM6RhXk5IU/0fCc=') + +var upsign = hmacsign('POST', 'http://api.twitter.com/1/statuses/update.json', + { oauth_consumer_key: "GDdmIQH6jhtmLUypg82g" + , oauth_nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" + , oauth_signature_method: "HMAC-SHA1" + , oauth_token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" + , oauth_timestamp: "1272325550" + , oauth_version: "1.0" + , status: 'setting up my twitter 私のさえずりを設定する' + }, "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98", "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA") + +console.log(upsign) +console.log('yOahq5m0YjDDjfjxHaXEsW9D+X0=') +assert.equal(upsign, 'yOahq5m0YjDDjfjxHaXEsW9D+X0=') + + +var rsign = request.post( + { url: 'https://api.twitter.com/oauth/request_token' + , oauth: + { callback: 'http://localhost:3005/the_dance/process_callback?service_provider_id=11' + , consumer_key: 'GDdmIQH6jhtmLUypg82g' + , nonce: 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk' + , timestamp: '1272323042' + , version: '1.0' + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + } + }) + +setTimeout(function () { + console.log(getsignature(rsign)) + assert.equal(reqsign, getsignature(rsign)) +}) + +var raccsign = request.post( + { url: 'https://api.twitter.com/oauth/access_token' + , oauth: + { consumer_key: 'GDdmIQH6jhtmLUypg82g' + , nonce: '9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8' + , signature_method: 'HMAC-SHA1' + , token: '8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc' + , timestamp: '1272323047' + , verifier: 'pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY' + , version: '1.0' + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + , token_secret: "x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA" + } + }) + +setTimeout(function () { + console.log(getsignature(raccsign)) + assert.equal(accsign, getsignature(raccsign)) +}, 1) + +var rupsign = request.post( + { url: 'http://api.twitter.com/1/statuses/update.json' + , oauth: + { consumer_key: "GDdmIQH6jhtmLUypg82g" + , nonce: "oElnnMTQIZvqvlfXM56aBLAf5noGD0AQR3Fmi7Q6Y" + , signature_method: "HMAC-SHA1" + , token: "819797-Jxq8aYUDRmykzVKrgoLhXSq67TEa5ruc4GJC2rWimw" + , timestamp: "1272325550" + , version: "1.0" + , consumer_secret: "MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98" + , token_secret: "J6zix3FfA9LofH0awS24M3HcBYXO5nI1iYe8EfBA" + } + , form: {status: 'setting up my twitter 私のさえずりを設定する'} + }) +setTimeout(function () { + console.log(getsignature(rupsign)) + assert.equal(upsign, getsignature(rupsign)) +}, 1) + + + + diff --git a/packages/wekan-request/tests/test-onelineproxy.js b/packages/wekan-request/tests/test-onelineproxy.js new file mode 100644 index 000000000..c239f8921 --- /dev/null +++ b/packages/wekan-request/tests/test-onelineproxy.js @@ -0,0 +1,46 @@ +var http = require('http') + , assert = require('assert') + , request = require('../index') + ; + +var server = http.createServer(function (req, resp) { + resp.statusCode = 200 + if (req.url === '/get') { + assert.equal(req.method, 'GET') + resp.write('content') + resp.end() + return + } + if (req.url === '/put') { + var x = '' + assert.equal(req.method, 'PUT') + req.on('data', function (chunk) { + x += chunk + }) + req.on('end', function () { + assert.equal(x, 'content') + resp.write('success') + resp.end() + }) + return + } + if (req.url === '/proxy') { + assert.equal(req.method, 'PUT') + return req.pipe(request('http://localhost:8080/put')).pipe(resp) + } + + if (req.url === '/test') { + return request('http://localhost:8080/get').pipe(request.put('http://localhost:8080/proxy')).pipe(resp) + } + throw new Error('Unknown url', req.url) +}).listen(8080, function () { + request('http://localhost:8080/test', function (e, resp, body) { + if (e) throw e + if (resp.statusCode !== 200) throw new Error('statusCode not 200 ' + resp.statusCode) + assert.equal(body, 'success') + server.close() + }) +}) + + + diff --git a/packages/wekan-request/tests/test-option-reuse.js b/packages/wekan-request/tests/test-option-reuse.js new file mode 100644 index 000000000..c3ac4906a --- /dev/null +++ b/packages/wekan-request/tests/test-option-reuse.js @@ -0,0 +1,38 @@ +var assert = require('assert') + , request = require('../index') + , http = require('http') + ; + +var count = 0; +var methodsSeen = { + head: 0 +, get: 0 +}; + +var s = http.createServer(function(req, res) { + res.statusCode = 200; + res.end(''); + count++; + + if (req.method.toLowerCase() === 'head') methodsSeen.head++; + if (req.method.toLowerCase() === 'get') methodsSeen.get++; + + if (count < 2) return + assert(methodsSeen.head === 1); + assert(methodsSeen.get === 1); +}).listen(6767, function () { + + //this is a simple check to see if the options object is be mutilated + var url = 'http://localhost:6767'; + var options = {url: url}; + + request.head(options, function (err, resp, body) { + assert(Object.keys(options).length === 1); + assert(options.url === url); + request.get(options, function (err, resp, body) { + assert(Object.keys(options).length === 1); + assert(options.url === url); + s.close(); + }) + }) +}) \ No newline at end of file diff --git a/packages/wekan-request/tests/test-params.js b/packages/wekan-request/tests/test-params.js new file mode 100644 index 000000000..a5831a140 --- /dev/null +++ b/packages/wekan-request/tests/test-params.js @@ -0,0 +1,93 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../index') + ; + +var s = server.createServer(); + +var tests = + { testGet : + { resp : server.createGetResponse("TESTING!") + , expectBody: "TESTING!" + } + , testGetChunkBreak : + { resp : server.createChunkResponse( + [ new Buffer([239]) + , new Buffer([163]) + , new Buffer([191]) + , new Buffer([206]) + , new Buffer([169]) + , new Buffer([226]) + , new Buffer([152]) + , new Buffer([131]) + ]) + , expectBody: "Ω☃" + } + , testGetBuffer : + { resp : server.createGetResponse(new Buffer("TESTING!")) + , encoding: null + , expectBody: new Buffer("TESTING!") + } + , testGetJSON : + { resp : server.createGetResponse('{"test":true}', 'application/json') + , json : true + , expectBody: {"test":true} + } + , testPutString : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : "PUTTINGDATA" + } + , testPutBuffer : + { resp : server.createPostValidator("PUTTINGDATA") + , method : "PUT" + , body : new Buffer("PUTTINGDATA") + } + , testPutJSON : + { resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) + , method: "PUT" + , json: {foo: 'bar'} + } + , testPutMultipart : + { resp: server.createPostValidator( + '--__BOUNDARY__\r\n' + + 'content-type: text/html\r\n' + + '\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__\r\n\r\n' + + 'Oh hi.' + + '\r\n--__BOUNDARY__--' + ) + , method: "PUT" + , multipart: + [ {'content-type': 'text/html', 'body': 'Oh hi.'} + , {'body': 'Oh hi.'} + ] + } + } + +s.listen(s.port, function () { + + var counter = 0 + + for (i in tests) { + (function () { + var test = tests[i] + s.on('/'+i, test.resp) + //test.uri = s.url + '/' + i + request(s.url + '/' + i, test, function (err, resp, body) { + if (err) throw err + if (test.expectBody) { + assert.deepEqual(test.expectBody, body) + } + counter = counter - 1; + if (counter === 0) { + assert.notEqual(typeof test.callback, 'function') + console.log(1 + Object.keys(tests).length+" tests passed.") + s.close() + } + }) + counter++ + })() + } +}) diff --git a/packages/wekan-request/tests/test-piped-redirect.js b/packages/wekan-request/tests/test-piped-redirect.js new file mode 100644 index 000000000..e295ec7fa --- /dev/null +++ b/packages/wekan-request/tests/test-piped-redirect.js @@ -0,0 +1,42 @@ +var http = require('http') + , assert = require('assert') + , request = require('../index') + ; + +var portOne = 8968 + , portTwo = 8969 + ; + + +// server one +var s1 = http.createServer(function (req, resp) { + if (req.url == '/original') { + resp.writeHeader(302, {'location': '/redirected'}) + resp.end() + } else if (req.url == '/redirected') { + resp.writeHeader(200, {'content-type': 'text/plain'}) + resp.write('OK') + resp.end() + } + +}).listen(portOne); + + +// server two +var s2 = http.createServer(function (req, resp) { + var x = request('http://localhost:'+portOne+'/original') + req.pipe(x) + x.pipe(resp) + +}).listen(portTwo, function () { + var r = request('http://localhost:'+portTwo+'/original', function (err, res, body) { + assert.equal(body, 'OK') + + s1.close() + s2.close() + }); + + // it hangs, so wait a second :) + r.timeout = 1000; + +}) diff --git a/packages/wekan-request/tests/test-pipes.js b/packages/wekan-request/tests/test-pipes.js new file mode 100644 index 000000000..ca1f58452 --- /dev/null +++ b/packages/wekan-request/tests/test-pipes.js @@ -0,0 +1,231 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../index') + , path = require('path') + , util = require('util') + ; + +var s = server.createServer(3453); + +function ValidationStream(str) { + this.str = str + this.buf = '' + this.on('data', function (data) { + this.buf += data + }) + this.on('end', function () { + assert.equal(this.str, this.buf) + }) + this.writable = true +} +util.inherits(ValidationStream, stream.Stream) +ValidationStream.prototype.write = function (chunk) { + this.emit('data', chunk) +} +ValidationStream.prototype.end = function (chunk) { + if (chunk) this.emit('data', chunk) + this.emit('end') +} + +s.listen(s.port, function () { + var counter = 0; + + var check = function () { + counter = counter - 1 + if (counter === 0) { + console.log('All tests passed.') + setTimeout(function () { + process.exit(); + }, 500) + } + } + + // Test pipeing to a request object + s.once('/push', server.createPostValidator("mydata")); + + var mydata = new stream.Stream(); + mydata.readable = true + + counter++ + var r1 = request.put({url:'http://localhost:3453/push'}, function () { + check(); + }) + mydata.pipe(r1) + + mydata.emit('data', 'mydata'); + mydata.emit('end'); + + // Test pipeing to a request object with a json body + s.once('/push-json', server.createPostValidator("{\"foo\":\"bar\"}", "application/json")); + + var mybodydata = new stream.Stream(); + mybodydata.readable = true + + counter++ + var r2 = request.put({url:'http://localhost:3453/push-json',json:true}, function () { + check(); + }) + mybodydata.pipe(r2) + + mybodydata.emit('data', JSON.stringify({foo:"bar"})); + mybodydata.emit('end'); + + // Test pipeing from a request object. + s.once('/pull', server.createGetResponse("mypulldata")); + + var mypulldata = new stream.Stream(); + mypulldata.writable = true + + counter++ + request({url:'http://localhost:3453/pull'}).pipe(mypulldata) + + var d = ''; + + mypulldata.write = function (chunk) { + d += chunk; + } + mypulldata.end = function () { + assert.equal(d, 'mypulldata'); + check(); + }; + + + s.on('/cat', function (req, resp) { + if (req.method === "GET") { + resp.writeHead(200, {'content-type':'text/plain-test', 'content-length':4}); + resp.end('asdf') + } else if (req.method === "PUT") { + assert.equal(req.headers['content-type'], 'text/plain-test'); + assert.equal(req.headers['content-length'], 4) + var validate = ''; + + req.on('data', function (chunk) {validate += chunk}) + req.on('end', function () { + resp.writeHead(201); + resp.end(); + assert.equal(validate, 'asdf'); + check(); + }) + } + }) + s.on('/pushjs', function (req, resp) { + if (req.method === "PUT") { + assert.equal(req.headers['content-type'], 'application/javascript'); + check(); + } + }) + s.on('/catresp', function (req, resp) { + request.get('http://localhost:3453/cat').pipe(resp) + }) + s.on('/doodle', function (req, resp) { + if (req.headers['x-oneline-proxy']) { + resp.setHeader('x-oneline-proxy', 'yup') + } + resp.writeHead('200', {'content-type':'image/jpeg'}) + fs.createReadStream(path.join(__dirname, 'googledoodle.jpg')).pipe(resp) + }) + s.on('/onelineproxy', function (req, resp) { + var x = request('http://localhost:3453/doodle') + req.pipe(x) + x.pipe(resp) + }) + + counter++ + fs.createReadStream(__filename).pipe(request.put('http://localhost:3453/pushjs')) + + counter++ + request.get('http://localhost:3453/cat').pipe(request.put('http://localhost:3453/cat')) + + counter++ + request.get('http://localhost:3453/catresp', function (e, resp, body) { + assert.equal(resp.headers['content-type'], 'text/plain-test'); + assert.equal(resp.headers['content-length'], 4) + check(); + }) + + var doodleWrite = fs.createWriteStream(path.join(__dirname, 'test.jpg')) + + counter++ + request.get('http://localhost:3453/doodle').pipe(doodleWrite) + + doodleWrite.on('close', function () { + assert.deepEqual(fs.readFileSync(path.join(__dirname, 'googledoodle.jpg')), fs.readFileSync(path.join(__dirname, 'test.jpg'))) + check() + }) + + process.on('exit', function () { + fs.unlinkSync(path.join(__dirname, 'test.jpg')) + }) + + counter++ + request.get({uri:'http://localhost:3453/onelineproxy', headers:{'x-oneline-proxy':'nope'}}, function (err, resp, body) { + assert.equal(resp.headers['x-oneline-proxy'], 'yup') + check() + }) + + s.on('/afterresponse', function (req, resp) { + resp.write('d') + resp.end() + }) + + counter++ + var afterresp = request.post('http://localhost:3453/afterresponse').on('response', function () { + var v = new ValidationStream('d') + afterresp.pipe(v) + v.on('end', check) + }) + + s.on('/forward1', function (req, resp) { + resp.writeHead(302, {location:'/forward2'}) + resp.end() + }) + s.on('/forward2', function (req, resp) { + resp.writeHead('200', {'content-type':'image/png'}) + resp.write('d') + resp.end() + }) + + counter++ + var validateForward = new ValidationStream('d') + validateForward.on('end', check) + request.get('http://localhost:3453/forward1').pipe(validateForward) + + // Test pipe options + s.once('/opts', server.createGetResponse('opts response')); + + var optsStream = new stream.Stream(); + optsStream.writable = true + + var optsData = ''; + optsStream.write = function (buf) { + optsData += buf; + if (optsData === 'opts response') { + setTimeout(check, 10); + } + } + + optsStream.end = function () { + assert.fail('end called') + }; + + counter++ + request({url:'http://localhost:3453/opts'}).pipe(optsStream, { end : false }) + + // test request.pipefilter is called correctly + counter++ + s.on('/pipefilter', function(req, resp) { + resp.end('d') + }) + var validatePipeFilter = new ValidationStream('d') + + var r3 = request.get('http://localhost:3453/pipefilter') + r3.pipe(validatePipeFilter) + r3.pipefilter = function(resp, dest) { + assert.equal(resp, r3.response) + assert.equal(dest, validatePipeFilter) + check() + } +}) diff --git a/packages/wekan-request/tests/test-pool.js b/packages/wekan-request/tests/test-pool.js new file mode 100644 index 000000000..791ee8b93 --- /dev/null +++ b/packages/wekan-request/tests/test-pool.js @@ -0,0 +1,16 @@ +var request = require('../index') + , http = require('http') + , assert = require('assert') + ; + +var s = http.createServer(function (req, resp) { + resp.statusCode = 200; + resp.end('asdf'); +}).listen(8080, function () { + request({'url': 'http://localhost:8080', 'pool': false}, function (e, resp) { + var agent = resp.request.agent; + assert.strictEqual(typeof agent, 'boolean'); + assert.strictEqual(agent, false); + s.close(); + }); +}); \ No newline at end of file diff --git a/packages/wekan-request/tests/test-protocol-changing-redirect.js b/packages/wekan-request/tests/test-protocol-changing-redirect.js new file mode 100644 index 000000000..7e83a41bd --- /dev/null +++ b/packages/wekan-request/tests/test-protocol-changing-redirect.js @@ -0,0 +1,61 @@ +var server = require('./server') + , assert = require('assert') + , request = require('../index') + + +var s = server.createServer() +var ss = server.createSSLServer() +var sUrl = 'http://localhost:' + s.port +var ssUrl = 'https://localhost:' + ss.port + +s.listen(s.port, bouncy(s, ssUrl)) +ss.listen(ss.port, bouncy(ss, sUrl)) + +var hits = {} +var expect = {} +var pending = 0 +function bouncy (s, server) { return function () { + + var redirs = { a: 'b' + , b: 'c' + , c: 'd' + , d: 'e' + , e: 'f' + , f: 'g' + , g: 'h' + , h: 'end' } + + var perm = true + Object.keys(redirs).forEach(function (p) { + var t = redirs[p] + + // switch type each time + var type = perm ? 301 : 302 + perm = !perm + s.on('/' + p, function (req, res) { + res.writeHead(type, { location: server + '/' + t }) + res.end() + }) + }) + + s.on('/end', function (req, res) { + var h = req.headers['x-test-key'] + hits[h] = true + pending -- + if (pending === 0) done() + }) +}} + +for (var i = 0; i < 5; i ++) { + pending ++ + var val = 'test_' + i + expect[val] = true + request({ url: (i % 2 ? sUrl : ssUrl) + '/a' + , headers: { 'x-test-key': val } + , rejectUnauthorized: false }) +} + +function done () { + assert.deepEqual(hits, expect) + process.exit(0) +} diff --git a/packages/wekan-request/tests/test-proxy-env.js b/packages/wekan-request/tests/test-proxy-env.js new file mode 100644 index 000000000..caeb33416 --- /dev/null +++ b/packages/wekan-request/tests/test-proxy-env.js @@ -0,0 +1,41 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../index') + , path = require('path') + , util = require('util') + ; + +var port = 6768 + , called = false + , proxiedHost = 'google.com' + ; + +// set up environment variable +process.env.HTTP_PROXY = 'http://localhost:'+port; + +var s = server.createServer(port) +s.listen(port, function () { + s.on('http://google.com/', function (req, res) { + called = true + assert.equal(req.headers.host, proxiedHost) + res.writeHeader(200) + res.end() + }) + request ({ + url: 'http://'+proxiedHost, + /* should read from HTTP_PROXY env var and + // behave as if these arguments where passed: + url: 'http://localhost:'+port, + headers: {host: proxiedHost} + //*/ + }, function (err, res, body) { + s.close() + }) +}) + +process.on('exit', function () { + assert.ok(called, 'the request must be made to the proxy server') +}) diff --git a/packages/wekan-request/tests/test-proxy-null.js b/packages/wekan-request/tests/test-proxy-null.js new file mode 100644 index 000000000..7688cf756 --- /dev/null +++ b/packages/wekan-request/tests/test-proxy-null.js @@ -0,0 +1,39 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../index') + , path = require('path') + , util = require('util') + ; + +var port = 6768 + , called = false + , proxiedHost = 'google.com' + ; + +// set up environment variable +process.env.HTTP_PROXY = 'http://localhost:'+port; + +var s = server.createServer(port) +s.listen(port, function () { + s.on('http://google.com/', function (req, res) { + called = true + assert.equal(req.headers.host, proxiedHost) + res.writeHeader(200) + res.end() + }) + request ({ + url: 'http://'+proxiedHost, + // should not read from HTTP_PROXY env var + proxy: null, + timeout: 500, + }, function (err, res, body) { + s.close() + }) +}) + +process.on('exit', function () { + assert.ok(!called, 'the request must not be made to the proxy server') +}) diff --git a/packages/wekan-request/tests/test-proxy.js b/packages/wekan-request/tests/test-proxy.js new file mode 100644 index 000000000..e183d68d1 --- /dev/null +++ b/packages/wekan-request/tests/test-proxy.js @@ -0,0 +1,39 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , fs = require('fs') + , request = require('../index') + , path = require('path') + , util = require('util') + ; + +var port = 6768 + , called = false + , proxiedHost = 'google.com' + ; + +var s = server.createServer(port) +s.listen(port, function () { + s.on('http://google.com/', function (req, res) { + called = true + assert.equal(req.headers.host, proxiedHost) + res.writeHeader(200) + res.end() + }) + request ({ + url: 'http://'+proxiedHost, + proxy: 'http://localhost:'+port + /* + //should behave as if these arguments where passed: + url: 'http://localhost:'+port, + headers: {host: proxiedHost} + //*/ + }, function (err, res, body) { + s.close() + }) +}) + +process.on('exit', function () { + assert.ok(called, 'the request must be made to the proxy server') +}) diff --git a/packages/wekan-request/tests/test-qs.js b/packages/wekan-request/tests/test-qs.js new file mode 100644 index 000000000..65958e6a3 --- /dev/null +++ b/packages/wekan-request/tests/test-qs.js @@ -0,0 +1,42 @@ +var request = request = require('../index') + , assert = require('assert') + ; + + +// Test adding a querystring +var req1 = request.get({ uri: 'http://www.google.com', qs: { q : 'search' }}) +setTimeout(function() { + assert.equal('/?q=search', req1.path) +}, 1) + +// Test replacing a querystring value +var req2 = request.get({ uri: 'http://www.google.com?q=abc', qs: { q : 'search' }}) +setTimeout(function() { + assert.equal('/?q=search', req2.path) +}, 1) + +// Test appending a querystring value to the ones present in the uri +var req3 = request.get({ uri: 'http://www.google.com?x=y', qs: { q : 'search' }}) +setTimeout(function() { + assert.equal('/?x=y&q=search', req3.path) +}, 1) + +// Test leaving a querystring alone +var req4 = request.get({ uri: 'http://www.google.com?x=y'}) +setTimeout(function() { + assert.equal('/?x=y', req4.path) +}, 1) + +// Test giving empty qs property +var req5 = request.get({ uri: 'http://www.google.com', qs: {}}) +setTimeout(function(){ + assert.equal('/', req5.path) +}, 1) + + +// Test modifying the qs after creating the request +var req6 = request.get({ uri: 'http://www.google.com', qs: {}}); +req6.qs({ q: "test" }); +process.nextTick(function() { + assert.equal('/?q=test', req6.path); +}); diff --git a/packages/wekan-request/tests/test-redirect.js b/packages/wekan-request/tests/test-redirect.js new file mode 100644 index 000000000..67274351c --- /dev/null +++ b/packages/wekan-request/tests/test-redirect.js @@ -0,0 +1,166 @@ +try { + require('tough-cookie') +} catch (e) { + console.error('tough-cookie must be installed to run this test.') + console.error('skipping this test. please install tough-cookie and run again if you need to test this feature.') + process.exit(0) +} + +var server = require('./server') + , assert = require('assert') + , request = require('../index') + ; + +var s = server.createServer() + +s.listen(s.port, function () { + var server = 'http://localhost:' + s.port; + var hits = {} + var passed = 0; + + bouncer(301, 'temp') + bouncer(302, 'perm') + bouncer(302, 'nope') + bouncer(307, 'fwd') + + function bouncer(code, label) { + var landing = label+'_landing'; + + s.on('/'+label, function (req, res) { + hits[label] = true; + res.writeHead(code, { + 'location':server + '/'+landing, + 'set-cookie': 'ham=eggs' + }) + res.end() + }) + + s.on('/'+landing, function (req, res) { + // Make sure the cookie doesn't get included twice, see #139: + // Make sure cookies are set properly after redirect + assert.equal(req.headers.cookie, 'foo=bar; quux=baz; ham=eggs'); + hits[landing] = true; + res.writeHead(200) + res.end(req.method.toUpperCase() + ' ' + landing) + }) + } + + // Permanent bounce + var jar = request.jar() + jar.setCookie('quux=baz', server); + request({uri: server+'/perm', jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 200) throw new Error('Status is not 200: '+res.statusCode) + assert.ok(hits.perm, 'Original request is to /perm') + assert.ok(hits.perm_landing, 'Forward to permanent landing URL') + assert.equal(body, 'GET perm_landing', 'Got permanent landing content') + passed += 1 + done() + }) + + // Temporary bounce + request({uri: server+'/temp', jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 200) throw new Error('Status is not 200: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(hits.temp_landing, 'Forward to temporary landing URL') + assert.equal(body, 'GET temp_landing', 'Got temporary landing content') + passed += 1 + done() + }) + + // Prevent bouncing. + request({uri:server+'/nope', jar: jar, headers: {cookie: 'foo=bar'}, followRedirect:false}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 302) throw new Error('Status is not 302: '+res.statusCode) + assert.ok(hits.nope, 'Original request to /nope') + assert.ok(!hits.nope_landing, 'No chasing the redirect') + assert.equal(res.statusCode, 302, 'Response is the bounce itself') + passed += 1 + done() + }) + + // Should not follow post redirects by default + request.post(server+'/temp', { jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 301) throw new Error('Status is not 301: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(!hits.temp_landing, 'No chasing the redirect when post') + assert.equal(res.statusCode, 301, 'Response is the bounce itself') + passed += 1 + done() + }) + + // Should follow post redirects when followAllRedirects true + request.post({uri:server+'/temp', followAllRedirects:true, jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 200) throw new Error('Status is not 200: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(hits.temp_landing, 'Forward to temporary landing URL') + assert.equal(body, 'GET temp_landing', 'Got temporary landing content') + passed += 1 + done() + }) + + request.post({uri:server+'/temp', followAllRedirects:false, jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 301) throw new Error('Status is not 301: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(!hits.temp_landing, 'No chasing the redirect') + assert.equal(res.statusCode, 301, 'Response is the bounce itself') + passed += 1 + done() + }) + + // Should not follow delete redirects by default + request.del(server+'/temp', { jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode < 301) throw new Error('Status is not a redirect.') + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(!hits.temp_landing, 'No chasing the redirect when delete') + assert.equal(res.statusCode, 301, 'Response is the bounce itself') + passed += 1 + done() + }) + + // Should not follow delete redirects even if followRedirect is set to true + request.del(server+'/temp', { followRedirect: true, jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 301) throw new Error('Status is not 301: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(!hits.temp_landing, 'No chasing the redirect when delete') + assert.equal(res.statusCode, 301, 'Response is the bounce itself') + passed += 1 + done() + }) + + // Should follow delete redirects when followAllRedirects true + request.del(server+'/temp', {followAllRedirects:true, jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 200) throw new Error('Status is not 200: '+res.statusCode) + assert.ok(hits.temp, 'Original request is to /temp') + assert.ok(hits.temp_landing, 'Forward to temporary landing URL') + assert.equal(body, 'GET temp_landing', 'Got temporary landing content') + passed += 1 + done() + }) + + request.del(server+'/fwd', {followAllRedirects:true, jar: jar, headers: {cookie: 'foo=bar'}}, function (er, res, body) { + if (er) throw er + if (res.statusCode !== 200) throw new Error('Status is not 200: '+res.statusCode) + assert.ok(hits.fwd, 'Original request is to /fwd') + assert.ok(hits.fwd_landing, 'Forward to temporary landing URL') + assert.equal(body, 'DELETE fwd_landing', 'Got temporary landing content') + passed += 1 + done() + }) + + var reqs_done = 0; + function done() { + reqs_done += 1; + if(reqs_done == 10) { + console.log(passed + ' tests passed.') + s.close() + } + } +}) diff --git a/packages/wekan-request/tests/test-timeout.js b/packages/wekan-request/tests/test-timeout.js new file mode 100644 index 000000000..e04d0e4c7 --- /dev/null +++ b/packages/wekan-request/tests/test-timeout.js @@ -0,0 +1,100 @@ +var server = require('./server') + , events = require('events') + , stream = require('stream') + , assert = require('assert') + , request = require('../index') + ; + +var s = server.createServer(); +var expectedBody = "waited"; +var remainingTests = 6; + +s.listen(s.port, function () { + // Request that waits for 200ms + s.on('/timeout', function (req, resp) { + setTimeout(function(){ + resp.writeHead(200, {'content-type':'text/plain'}) + resp.write(expectedBody) + resp.end() + }, 200); + }); + + // Scenario that should timeout + var shouldTimeout = { + url: s.url + "/timeout", + timeout:100 + } + + + request(shouldTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + + var shouldTimeoutWithEvents = { + url: s.url + "/timeout", + timeout:100 + } + + var eventsEmitted = 0; + request(shouldTimeoutWithEvents) + .on('error', function (err) { + eventsEmitted++; + assert.equal(err.code, eventsEmitted == 1 ? "ETIMEDOUT" : "ECONNRESET"); + checkDone(); + }) + + // Scenario that shouldn't timeout + var shouldntTimeout = { + url: s.url + "/timeout", + timeout:300 + } + + request(shouldntTimeout, function (err, resp, body) { + assert.equal(err, null); + assert.equal(expectedBody, body) + checkDone(); + }) + + // Scenario with no timeout set, so shouldn't timeout + var noTimeout = { + url: s.url + "/timeout" + } + + request(noTimeout, function (err, resp, body) { + assert.equal(err); + assert.equal(expectedBody, body) + checkDone(); + }) + + // Scenario with a negative timeout value, should be treated a zero or the minimum delay + var negativeTimeout = { + url: s.url + "/timeout", + timeout:-1000 + } + + request(negativeTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + // Scenario with a float timeout value, should be rounded by setTimeout anyway + var floatTimeout = { + url: s.url + "/timeout", + timeout: 100.76 + } + + request(floatTimeout, function (err, resp, body) { + assert.equal(err.code, "ETIMEDOUT"); + checkDone(); + }) + + function checkDone() { + if(--remainingTests == 0) { + s.close(); + console.log("All tests passed."); + } + } +}) + diff --git a/packages/wekan-request/tests/test-toJSON.js b/packages/wekan-request/tests/test-toJSON.js new file mode 100644 index 000000000..6d5f92aaa --- /dev/null +++ b/packages/wekan-request/tests/test-toJSON.js @@ -0,0 +1,14 @@ +var request = require('../index') + , http = require('http') + , assert = require('assert') + ; + +var s = http.createServer(function (req, resp) { + resp.statusCode = 200 + resp.end('asdf') +}).listen(8080, function () { + var r = request('http://localhost:8080', function (e, resp) { + assert.equal(JSON.parse(JSON.stringify(r)).response.statusCode, 200) + s.close() + }) +}) \ No newline at end of file diff --git a/packages/wekan-request/tests/test-tunnel.js b/packages/wekan-request/tests/test-tunnel.js new file mode 100644 index 000000000..2ee3f393e --- /dev/null +++ b/packages/wekan-request/tests/test-tunnel.js @@ -0,0 +1,75 @@ +// test that we can tunnel a https request over an http proxy +// keeping all the CA and whatnot intact. +// +// Note: this requires that squid is installed. +// If the proxy fails to start, we'll just log a warning and assume success. + +var server = require('./server') + , assert = require('assert') + , request = require('../index') + , fs = require('fs') + , path = require('path') + , caFile = path.resolve(__dirname, 'ssl/npm-ca.crt') + , ca = fs.readFileSync(caFile) + , child_process = require('child_process') + , sqConf = path.resolve(__dirname, 'squid.conf') + , sqArgs = ['-f', sqConf, '-N', '-d', '5'] + , proxy = 'http://localhost:3128' + , hadError = null + +var squid = child_process.spawn('squid', sqArgs); +var ready = false + +squid.stderr.on('data', function (c) { + console.error('SQUIDERR ' + c.toString().trim().split('\n') + .join('\nSQUIDERR ')) + ready = c.toString().match(/ready to serve requests|Accepting HTTP Socket connections/i) +}) + +squid.stdout.on('data', function (c) { + console.error('SQUIDOUT ' + c.toString().trim().split('\n') + .join('\nSQUIDOUT ')) +}) + +squid.on('error', function (c) { + console.error('squid: error '+c) + if (c && !ready) { + notInstalled() + return + } +}) + +squid.on('exit', function (c) { + console.error('squid: exit '+c) + if (c && !ready) { + notInstalled() + return + } + + if (c) { + hadError = hadError || new Error('Squid exited with '+c) + } + if (hadError) throw hadError +}) + +setTimeout(function F () { + if (!ready) return setTimeout(F, 100) + request({ uri: 'https://registry.npmjs.org/' + , proxy: 'http://localhost:3128' + , strictSSL: true + , ca: ca + , json: true }, function (er, body) { + hadError = er + console.log(er || typeof body) + if (!er) console.log("ok") + squid.kill('SIGKILL') + }) +}, 100) + +function notInstalled() { + console.error('squid must be installed to run this test.') + console.error('skipping this test. please install squid and run again if you need to test tunneling.') + c = null + hadError = null + process.exit(0) +} diff --git a/packages/wekan-request/tests/test-unix.js b/packages/wekan-request/tests/test-unix.js new file mode 100644 index 000000000..342905f7b --- /dev/null +++ b/packages/wekan-request/tests/test-unix.js @@ -0,0 +1,31 @@ +var assert = require('assert') + , request = require('../index') + , http = require('http') + , fs = require('fs') + ; + +var path = [null, 'test', 'path'].join('/'); +var socket = [__dirname, 'tmp-socket'].join('/'); +var body = 'connected'; +var statusCode = 200; + +var s = http.createServer(function(req, res) { + // Assert requested path is sent to server + assert.equal(req.url, path); + res.statusCode = statusCode; + res.end(body); +}).listen(socket, function () { + + request(['unix://', socket, path].join(''), function (error, response, response_body) { + // Assert no error in connection + assert.equal(error, null); + // Assert http success status code + assert.equal(response.statusCode, statusCode); + // Assert expected response body is recieved + assert.equal(response_body, body); + // clean up + s.close(); + fs.unlink(socket, function(){}); + }) + +}) \ No newline at end of file diff --git a/packages/wekan-request/tests/unicycle.jpg b/packages/wekan-request/tests/unicycle.jpg new file mode 100644 index 000000000..7cea4dd71 Binary files /dev/null and b/packages/wekan-request/tests/unicycle.jpg differ diff --git a/packages/wekan-scrollbar/.gitignore b/packages/wekan-scrollbar/.gitignore deleted file mode 100644 index 8d1958474..000000000 --- a/packages/wekan-scrollbar/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -.build* - -# Ignore OS generated files -.DS_Store -.DS_Store? -._* -.Trashes -Thumbs.db -ehthumbs.db - -#Ignore temp files -*~ - -.idea \ No newline at end of file diff --git a/packages/wekan-scrollbar/jquery.mCustomScrollbar.css b/packages/wekan-scrollbar/jquery.mCustomScrollbar.css deleted file mode 100644 index 45152c1be..000000000 --- a/packages/wekan-scrollbar/jquery.mCustomScrollbar.css +++ /dev/null @@ -1,1267 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -*/ - - - -/* -CONTENTS: - 1. BASIC STYLE - Plugin's basic/essential CSS properties (normally, should not be edited). - 2. VERTICAL SCROLLBAR - Positioning and dimensions of vertical scrollbar. - 3. HORIZONTAL SCROLLBAR - Positioning and dimensions of horizontal scrollbar. - 4. VERTICAL AND HORIZONTAL SCROLLBARS - Positioning and dimensions of 2-axis scrollbars. - 5. TRANSITIONS - CSS3 transitions for hover events, auto-expanded and auto-hidden scrollbars. - 6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS - 6.1 THEMES - Scrollbar colors, opacity, dimensions, backgrounds etc. via ready-to-use themes. -*/ - - - -/* ------------------------------------------------------------------------------------------------------------------------- -1. BASIC STYLE ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCustomScrollbar{ -ms-touch-action: pinch-zoom; touch-action: pinch-zoom; /* direct pointer events to js */ } - .mCustomScrollbar.mCS_no_scrollbar, .mCustomScrollbar.mCS_touch_action{ -ms-touch-action: auto; touch-action: auto; } - - .mCustomScrollBox{ /* contains plugin's markup */ - position: relative; - overflow: hidden; - height: 100%; - max-width: 100%; - outline: none; - direction: ltr; - } - - .mCSB_container{ /* contains the original content */ - overflow: hidden; - width: auto; - height: auto; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -2. VERTICAL SCROLLBAR -y-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_inside > .mCSB_container{ margin-right: 30px; } - - .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-right: 0; } /* non-visible scrollbar */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-left: 0; } /* RTL direction/left-side scrollbar */ - - .mCSB_scrollTools{ /* contains scrollbar markup (draggable element, dragger rail, buttons etc.) */ - position: absolute; - width: 16px; - height: auto; - left: auto; - top: 0; - right: 0; - bottom: 0; - } - - .mCSB_outside + .mCSB_scrollTools{ right: -26px; } /* scrollbar position: outside */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_scrollTools, - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ /* RTL direction/left-side scrollbar */ - right: auto; - left: 0; - } - - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ left: -26px; } /* RTL direction/left-side scrollbar (scrollbar position: outside) */ - - .mCSB_scrollTools .mCSB_draggerContainer{ /* contains the draggable element and dragger rail markup */ - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - height: auto; - } - - .mCSB_scrollTools a + .mCSB_draggerContainer{ margin: 20px 0; } - - .mCSB_scrollTools .mCSB_draggerRail{ - width: 2px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - } - - .mCSB_scrollTools .mCSB_dragger{ /* the draggable element */ - cursor: pointer; - width: 100%; - height: 30px; /* minimum dragger height */ - z-index: 1; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ /* the dragger element */ - position: relative; - width: 4px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - text-align: center; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ width: 12px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 8px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown{ - display: block; - position: absolute; - height: 20px; - width: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools .mCSB_buttonDown{ bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -3. HORIZONTAL SCROLLBAR -x-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_horizontal.mCSB_inside > .mCSB_container{ - margin-right: 0; - margin-bottom: 30px; - } - - .mCSB_horizontal.mCSB_outside > .mCSB_container{ min-height: 100%; } - - .mCSB_horizontal > .mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } /* non-visible scrollbar */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - width: auto; - height: 16px; - top: auto; - right: 0; - bottom: 0; - left: 0; - } - - .mCustomScrollBox + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ bottom: -26px; } /* scrollbar position: outside */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal a + .mCSB_draggerContainer{ margin: 0 20px; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 2px; - margin: 7px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger{ - width: 30px; /* minimum dragger width */ - height: 100%; - left: 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; /* auto-expanded scrollbar */ - margin: 2px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 8px; /* auto-expanded scrollbar */ - margin: 4px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft, - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ - display: block; - position: absolute; - width: 20px; - height: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft{ left: 0; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ right: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -4. VERTICAL AND HORIZONTAL SCROLLBARS -yx-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_container_wrapper{ - position: absolute; - height: auto; - width: auto; - overflow: hidden; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin-right: 30px; - margin-bottom: 30px; - } - - .mCSB_container_wrapper > .mCSB_container{ - padding-right: 30px; - padding-bottom: 30px; - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 20px; } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 20px; } - - /* non-visible horizontal scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden + .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 0; } - - /* non-visible vertical scrollbar/RTL direction/left-side scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 0; } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 20px; } - - /* non-visible scrollbar/RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 0; } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container_wrapper{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden > .mCSB_container{ padding-right: 0; } - - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden > .mCSB_container{ padding-bottom: 0; } - - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden{ - margin-right: 0; /* non-visible scrollbar */ - margin-left: 0; - } - - /* non-visible horizontal scrollbar */ - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -5. TRANSITIONS ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_scrollTools, - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - -webkit-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, /* auto-expanded scrollbar */ - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail{ - -webkit-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS ------------------------------------------------------------------------------------------------------------------------- -*/ - - /* - ---------------------------------------- - 6.1 THEMES - ---------------------------------------- - */ - - /* default theme ("light") */ - - .mCSB_scrollTools{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 0; filter: "alpha(opacity=0)"; -ms-filter: "alpha(opacity=0)"; } - - .mCustomScrollbar > .mCustomScrollBox > .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollbar > .mCustomScrollBox ~ .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollBox:hover > .mCSB_scrollTools, - .mCustomScrollBox:hover ~ .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 1; filter: "alpha(opacity=100)"; -ms-filter: "alpha(opacity=100)"; } - - .mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.4); - filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.75); - filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; - } - - .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.85); - filter: "alpha(opacity=85)"; -ms-filter: "alpha(opacity=85)"; - } - .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.9); - filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; - } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - background-image: url(mCSB_buttons.png); /* css sprites */ - background-repeat: no-repeat; - opacity: 0.4; filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_buttonUp{ - background-position: 0 0; - /* - sprites locations - light: 0 0, -16px 0, -32px 0, -48px 0, 0 -72px, -16px -72px, -32px -72px - dark: -80px 0, -96px 0, -112px 0, -128px 0, -80px -72px, -96px -72px, -112px -72px - */ - } - - .mCSB_scrollTools .mCSB_buttonDown{ - background-position: 0 -20px; - /* - sprites locations - light: 0 -20px, -16px -20px, -32px -20px, -48px -20px, 0 -92px, -16px -92px, -32px -92px - dark: -80px -20px, -96px -20px, -112px -20px, -128px -20px, -80px -92px, -96px -92px, -112 -92px - */ - } - - .mCSB_scrollTools .mCSB_buttonLeft{ - background-position: 0 -40px; - /* - sprites locations - light: 0 -40px, -20px -40px, -40px -40px, -60px -40px, 0 -112px, -20px -112px, -40px -112px - dark: -80px -40px, -100px -40px, -120px -40px, -140px -40px, -80px -112px, -100px -112px, -120px -112px - */ - } - - .mCSB_scrollTools .mCSB_buttonRight{ - background-position: 0 -56px; - /* - sprites locations - light: 0 -56px, -20px -56px, -40px -56px, -60px -56px, 0 -128px, -20px -128px, -40px -128px - dark: -80px -56px, -100px -56px, -120px -56px, -140px -56px, -80px -128px, -100px -128px, -120px -128px - */ - } - - .mCSB_scrollTools .mCSB_buttonUp:hover, - .mCSB_scrollTools .mCSB_buttonDown:hover, - .mCSB_scrollTools .mCSB_buttonLeft:hover, - .mCSB_scrollTools .mCSB_buttonRight:hover{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCSB_scrollTools .mCSB_buttonUp:active, - .mCSB_scrollTools .mCSB_buttonDown:active, - .mCSB_scrollTools .mCSB_buttonLeft:active, - .mCSB_scrollTools .mCSB_buttonRight:active{ opacity: 0.9; filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; } - - - /* theme: "dark" */ - - .mCS-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.85); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.9); } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-2", "dark-2" */ - - .mCS-light-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px 0; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -20px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -40px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -56px; } - - - /* theme: "dark-2" */ - - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px 0; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -20px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -40px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thick", "dark-thick" */ - - .mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 4px; - margin: 6px 0; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 6px; - margin: 5px auto; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px 0; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -20px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -40px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -56px; } - - - /* theme: "dark-thick" */ - - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px 0; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -20px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -40px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thin", "dark-thin" */ - - .mCS-light-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.1); } - - .mCS-light-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 2px; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail{ width: 100%; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 2px; - margin: 7px auto; - } - - - /* theme "dark-thin" */ - - .mCS-dark-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme "rounded", "rounded-dark", "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.15); } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger{ height: 14px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 14px; - margin: 0 1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 14px; } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 14px; - margin: 1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - width: 16px; /* auto-expanded scrollbar */ - height: 16px; - margin: -1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 4px; /* auto-expanded scrollbar */ } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 16px; /* auto-expanded scrollbar */ - width: 16px; - margin: 0 -1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 4px; /* auto-expanded scrollbar */ - margin: 6px 0; - } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonUp{ background-position: 0 -72px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonDown{ background-position: 0 -92px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonLeft{ background-position: 0 -112px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonRight{ background-position: 0 -128px; } - - - /* theme "rounded-dark", "rounded-dots-dark" */ - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px -72px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -92px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -112px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -128px; } - - - /* theme "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded-dots.mCSB_scrollTools_vertical .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_vertical .mCSB_draggerRail{ width: 4px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - background-color: transparent; - background-position: center; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAANElEQVQYV2NkIAAYiVbw//9/Y6DiM1ANJoyMjGdBbLgJQAX/kU0DKgDLkaQAvxW4HEvQFwCRcxIJK1XznAAAAABJRU5ErkJggg=="); - background-repeat: repeat-y; - opacity: 0.3; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - height: 4px; - margin: 6px 0; - background-repeat: repeat-x; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px -72px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -92px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -112px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -128px; } - - - /* theme "rounded-dots-dark" */ - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAALElEQVQYV2NkIAAYSVFgDFR8BqrBBEifBbGRTfiPZhpYjiQFBK3A6l6CvgAAE9kGCd1mvgEAAAAASUVORK5CYII="); - } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px -72px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -92px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -112px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "3d", "3d-dark", "3d-thick", "3d-thick-dark" */ - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-y; - background-image: -moz-linear-gradient(left, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to right, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-x; - background-image: -moz-linear-gradient(top, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to bottom, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - - /* theme "3d", "3d-dark" */ - - .mCS-3d.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 70px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 70px; } - - .mCS-3d.mCSB_scrollTools, - .mCS-3d-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 8px; - background-color: #000; background-color: rgba(0,0,0,0.2); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.5), inset -1px 0 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 8px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 8px; - margin: 4px 0; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.5), inset 0 -1px 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 8px; - margin: 4px auto; - } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "3d-dark" */ - - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.1); - } - - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "3d-thick", "3d-thick-dark" */ - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools, - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } - - .mCSB_inside + .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCSB_inside + .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ right: 1px; } - - .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ box-shadow: inset 1px 0 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); } - - .mCS-3d-thick.mCSB_scrollTools_horizontal, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ - bottom: 1px; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - box-shadow: inset 1px 0 0 rgba(255,255,255,0.4); - width: 12px; - margin: 2px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4); } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; - width: auto; - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #000; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme: "3d-thick-dark" */ - - .mCS-3d-thick-dark.mCSB_scrollTools{ box-shadow: inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 1px 0 0 rgba(255,255,255,0.4), inset -1px 0 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #777; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #fff; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "minimal", "minimal-dark" */ - - .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - right: 0; - margin: 12px 0; - } - - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - bottom: 0; - margin: 0 12px; - } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - left: 0; - right: auto; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_draggerRail, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-minimal.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 50px; } - - .mCS-minimal.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 50px; } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - - /* theme: "minimal-dark" */ - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - /* ---------------------------------------- */ - - - - /* theme "light-3", "dark-3" */ - - .mCS-light-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ - width: 6px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-light-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 6px; } - - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 6px; - margin: 5px 0; - } - - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - width: 12px; - } - - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 12px; - margin: 2px 0; - } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "dark-3" */ - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "inset", "inset-dark", "inset-2", "inset-2-dark", "inset-3", "inset-3-dark" */ - - .mCS-inset.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 12px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-inset.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - margin: 3px 5px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 6px; - margin: 5px 3px; - position: absolute; - width: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 12px; - margin: 2px 0; - } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "inset-dark", "inset-2-dark", "inset-3-dark" */ - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - - /* theme "inset-2", "inset-2-dark" */ - - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: transparent; - border-width: 1px; - border-style: solid; - border-color: #fff; - border-color: rgba(255,255,255,0.2); - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ border-color: #000; border-color: rgba(0,0,0,0.2); } - - - /* theme "inset-3", "inset-3-dark" */ - - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.6); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.6); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.75); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - /* ---------------------------------------- */ diff --git a/packages/wekan-scrollbar/jquery.mCustomScrollbar.js b/packages/wekan-scrollbar/jquery.mCustomScrollbar.js deleted file mode 100644 index e7d499975..000000000 --- a/packages/wekan-scrollbar/jquery.mCustomScrollbar.js +++ /dev/null @@ -1,2423 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Version: 3.1.3 -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -Author: malihu -Author URI: http://manos.malihu.gr -License: MIT License (MIT) -*/ - -/* -Copyright Manos Malihutsakis (email: manos@malihu.gr) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -/* -The code below is fairly long, fully commented and should be normally used in development. -For production, use either the minified jquery.mCustomScrollbar.min.js script or -the production-ready jquery.mCustomScrollbar.concat.min.js which contains the plugin -and dependencies (minified). -*/ - -(function(factory){ - if(typeof module!=="undefined" && module.exports){ - module.exports=factory; - }else{ - factory(jQuery,window,document); - } -}(function($){ -(function(init){ - var _rjs=typeof define==="function" && define.amd, /* RequireJS */ - _njs=typeof module !== "undefined" && module.exports; /* NodeJS */ - if(!_rjs){ - if(_njs){ - require("jquery-mousewheel")($); - }else{ - /* Only load locally jquery-mousewheel plugin. - (works when mCustomScrollbar fn is called on window load) */ - $.event.special.mousewheel; - } - } - init(); -}(function(){ - - /* - ---------------------------------------- - PLUGIN NAMESPACE, PREFIX, DEFAULT SELECTOR(S) - ---------------------------------------- - */ - - var pluginNS="mCustomScrollbar", - pluginPfx="mCS", - defaultSelector=".mCustomScrollbar", - - - - - - /* - ---------------------------------------- - DEFAULT OPTIONS - ---------------------------------------- - */ - - defaults={ - /* - set element/content width/height programmatically - values: boolean, pixels, percentage - option default - ------------------------------------- - setWidth false - setHeight false - */ - /* - set the initial css top property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setTop:0, - /* - set the initial css left property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setLeft:0, - /* - scrollbar axis (vertical and/or horizontal scrollbars) - values (string): "y", "x", "yx" - */ - axis:"y", - /* - position of scrollbar relative to content - values (string): "inside", "outside" ("outside" requires elements with position:relative) - */ - scrollbarPosition:"inside", - /* - scrolling inertia - values: integer (milliseconds). default: 0 - */ - scrollInertia:Meteor.settings.public.SCROLLINERTIA, - /* - auto-adjust scrollbar dragger length - values: boolean - */ - autoDraggerLength:true, - /* - auto-hide scrollbar when idle - values: boolean - option default - ------------------------------------- - autoHideScrollbar false - */ - /* - auto-expands scrollbar on mouse-over and dragging - values: boolean - option default - ------------------------------------- - autoExpandScrollbar false - */ - /* - always show scrollbar, even when there's nothing to scroll - values: integer (0=disable, 1=always show dragger rail and buttons, 2=always show dragger rail, dragger and buttons), boolean - */ - alwaysShowScrollbar:0, - /* - scrolling always snaps to a multiple of this number in pixels - values: integer, array ([y,x]) - option default - ------------------------------------- - snapAmount null - */ - /* - when snapping, snap with this number in pixels as an offset - values: integer - */ - snapOffset:0, - /* - mouse-wheel scrolling - */ - mouseWheel:{ - /* - enable mouse-wheel scrolling - values: boolean - */ - enable:true, - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:Meteor.settings.public.SCROLLAMOUNT, - /* - mouse-wheel scrolling axis - the default scrolling direction when both vertical and horizontal scrollbars are present - values (string): "y", "x" - */ - axis:"y", - /* - prevent the default behaviour which automatically scrolls the parent element(s) when end of scrolling is reached - values: boolean - option default - ------------------------------------- - preventDefault null - */ - /* - the reported mouse-wheel delta value. The number of lines (translated to pixels) one wheel notch scrolls. - values: "auto", integer - "auto" uses the default OS/browser value - */ - deltaFactor:"auto", - /* - normalize mouse-wheel delta to -1 or 1 (disables mouse-wheel acceleration) - values: boolean - option default - ------------------------------------- - normalizeDelta null - */ - /* - invert mouse-wheel scrolling direction - values: boolean - option default - ------------------------------------- - invert null - */ - /* - the tags that disable mouse-wheel when cursor is over them - */ - disableOver:["select","option","keygen","datalist","textarea"] - }, - /* - scrollbar buttons - */ - scrollButtons:{ - /* - enable scrollbar buttons - values: boolean - option default - ------------------------------------- - enable null - */ - /* - scrollbar buttons scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - /* - tabindex of the scrollbar buttons - values: false, integer - option default - ------------------------------------- - tabindex null - */ - }, - /* - keyboard scrolling - */ - keyboard:{ - /* - enable scrolling via keyboard - values: boolean - */ - enable:true, - /* - keyboard scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - }, - /* - enable content touch-swipe scrolling - values: boolean, integer, string (number) - integer values define the axis-specific minimum amount required for scrolling momentum - */ - contentTouchScroll:25, - /* - enable/disable document (default) touch-swipe scrolling - */ - documentTouchScroll:true, - /* - advanced option parameters - */ - advanced:{ - /* - auto-expand content horizontally (for "x" or "yx" axis) - values: boolean, integer (the value 2 forces the non scrollHeight/scrollWidth method, the value 3 forces the scrollHeight/scrollWidth method) - option default - ------------------------------------- - autoExpandHorizontalScroll null - */ - /* - auto-scroll to elements with focus - */ - autoScrollOnFocus:"input,textarea,select,button,datalist,keygen,a[tabindex],area,object,[contenteditable='true']", - /* - auto-update scrollbars on content, element or viewport resize - should be true for fluid layouts/elements, adding/removing content dynamically, hiding/showing elements, content with images etc. - values: boolean - */ - updateOnContentResize:true, - /* - auto-update scrollbars each time each image inside the element is fully loaded - values: "auto", boolean - */ - updateOnImageLoad:"auto", - /* - auto-update scrollbars based on the amount and size changes of specific selectors - useful when you need to update the scrollbar(s) automatically, each time a type of element is added, removed or changes its size - values: boolean, string (e.g. "ul li" will auto-update scrollbars each time list-items inside the element are changed) - a value of true (boolean) will auto-update scrollbars each time any element is changed - option default - ------------------------------------- - updateOnSelectorChange null - */ - /* - extra selectors that'll allow scrollbar dragging upon mousemove/up, pointermove/up, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - extraDraggableSelectors null - */ - /* - extra selectors that'll release scrollbar dragging upon mouseup, pointerup, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - releaseDraggableSelectors null - */ - /* - auto-update timeout - values: integer (milliseconds) - */ - autoUpdateTimeout:60 - }, - /* - scrollbar theme - values: string (see CSS/plugin URI for a list of ready-to-use themes) - */ - theme:"light", - /* - user defined callback functions - */ - callbacks:{ - /* - Available callbacks: - callback default - ------------------------------------- - onCreate null - onInit null - onScrollStart null - onScroll null - onTotalScroll null - onTotalScrollBack null - whileScrolling null - onOverflowY null - onOverflowX null - onOverflowYNone null - onOverflowXNone null - onImageLoad null - onSelectorChange null - onBeforeUpdate null - onUpdate null - */ - onTotalScrollOffset:0, - onTotalScrollBackOffset:0, - alwaysTriggerOffsets:true - } - /* - add scrollbar(s) on all elements matching the current selector, now and in the future - values: boolean, string - string values: "on" (enable), "once" (disable after first invocation), "off" (disable) - liveSelector values: string (selector) - option default - ------------------------------------- - live false - liveSelector null - */ - }, - - - - - - /* - ---------------------------------------- - VARS, CONSTANTS - ---------------------------------------- - */ - - totalInstances=0, /* plugin instances amount */ - liveTimers={}, /* live option timers */ - oldIE=(window.attachEvent && !window.addEventListener) ? 1 : 0, /* detect IE < 9 */ - touchActive=false,touchable, /* global touch vars (for touch and pointer events) */ - /* general plugin classes */ - classes=[ - "mCSB_dragger_onDrag","mCSB_scrollTools_onDrag","mCS_img_loaded","mCS_disabled","mCS_destroyed","mCS_no_scrollbar", - "mCS-autoHide","mCS-dir-rtl","mCS_no_scrollbar_y","mCS_no_scrollbar_x","mCS_y_hidden","mCS_x_hidden","mCSB_draggerContainer", - "mCSB_buttonUp","mCSB_buttonDown","mCSB_buttonLeft","mCSB_buttonRight" - ], - - - - - - /* - ---------------------------------------- - METHODS - ---------------------------------------- - */ - - methods={ - - /* - plugin initialization method - creates the scrollbar(s), plugin data object and options - ---------------------------------------- - */ - - init:function(options){ - - var options=$.extend(true,{},defaults,options), - selector=_selector.call(this); /* validate selector */ - - /* - if live option is enabled, monitor for elements matching the current selector and - apply scrollbar(s) when found (now and in the future) - */ - if(options.live){ - var liveSelector=options.liveSelector || this.selector || defaultSelector, /* live selector(s) */ - $liveSelector=$(liveSelector); /* live selector(s) as jquery object */ - if(options.live==="off"){ - /* - disable live if requested - usage: $(selector).mCustomScrollbar({live:"off"}); - */ - removeLiveTimers(liveSelector); - return; - } - liveTimers[liveSelector]=setTimeout(function(){ - /* call mCustomScrollbar fn on live selector(s) every half-second */ - $liveSelector.mCustomScrollbar(options); - if(options.live==="once" && $liveSelector.length){ - /* disable live after first invocation */ - removeLiveTimers(liveSelector); - } - },500); - }else{ - removeLiveTimers(liveSelector); - } - - /* options backward compatibility (for versions < 3.0.0) and normalization */ - options.setWidth=(options.set_width) ? options.set_width : options.setWidth; - options.setHeight=(options.set_height) ? options.set_height : options.setHeight; - options.axis=(options.horizontalScroll) ? "x" : _findAxis(options.axis); - options.scrollInertia=options.scrollInertia>0 && options.scrollInertia<17 ? 17 : options.scrollInertia; - if(typeof options.mouseWheel!=="object" && options.mouseWheel==true){ /* old school mouseWheel option (non-object) */ - options.mouseWheel={enable:true,scrollAmount:"auto",axis:"y",preventDefault:false,deltaFactor:"auto",normalizeDelta:false,invert:false} - } - options.mouseWheel.scrollAmount=!options.mouseWheelPixels ? options.mouseWheel.scrollAmount : options.mouseWheelPixels; - options.mouseWheel.normalizeDelta=!options.advanced.normalizeMouseWheelDelta ? options.mouseWheel.normalizeDelta : options.advanced.normalizeMouseWheelDelta; - options.scrollButtons.scrollType=_findScrollButtonsType(options.scrollButtons.scrollType); - - _theme(options); /* theme-specific options */ - - /* plugin constructor */ - return $(selector).each(function(){ - - var $this=$(this); - - if(!$this.data(pluginPfx)){ /* prevent multiple instantiations */ - - /* store options and create objects in jquery data */ - $this.data(pluginPfx,{ - idx:++totalInstances, /* instance index */ - opt:options, /* options */ - scrollRatio:{y:null,x:null}, /* scrollbar to content ratio */ - overflowed:null, /* overflowed axis */ - contentReset:{y:null,x:null}, /* object to check when content resets */ - bindEvents:false, /* object to check if events are bound */ - tweenRunning:false, /* object to check if tween is running */ - sequential:{}, /* sequential scrolling object */ - langDir:$this.css("direction"), /* detect/store direction (ltr or rtl) */ - cbOffsets:null, /* object to check whether callback offsets always trigger */ - /* - object to check how scrolling events where last triggered - "internal" (default - triggered by this script), "external" (triggered by other scripts, e.g. via scrollTo method) - usage: object.data("mCS").trigger - */ - trigger:null, - /* - object to check for changes in elements in order to call the update method automatically - */ - poll:{size:{o:0,n:0},img:{o:0,n:0},change:{o:0,n:0}} - }); - - var d=$this.data(pluginPfx),o=d.opt, - /* HTML data attributes */ - htmlDataAxis=$this.data("mcs-axis"),htmlDataSbPos=$this.data("mcs-scrollbar-position"),htmlDataTheme=$this.data("mcs-theme"); - - if(htmlDataAxis){o.axis=htmlDataAxis;} /* usage example: data-mcs-axis="y" */ - if(htmlDataSbPos){o.scrollbarPosition=htmlDataSbPos;} /* usage example: data-mcs-scrollbar-position="outside" */ - if(htmlDataTheme){ /* usage example: data-mcs-theme="minimal" */ - o.theme=htmlDataTheme; - _theme(o); /* theme-specific options */ - } - - _pluginMarkup.call(this); /* add plugin markup */ - - if(d && o.callbacks.onCreate && typeof o.callbacks.onCreate==="function"){o.callbacks.onCreate.call(this);} /* callbacks: onCreate */ - - $("#mCSB_"+d.idx+"_container img:not(."+classes[2]+")").addClass(classes[2]); /* flag loaded images */ - - methods.update.call(null,$this); /* call the update method */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin update method - updates content and scrollbar(s) values, events and status - ---------------------------------------- - usage: $(selector).mCustomScrollbar("update"); - */ - - update:function(el,cb){ - - var selector=el || _selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - - if(!mCSB_container.length){return;} - - if(d.tweenRunning){_stop($this);} /* stop any running tweens while updating */ - - if(cb && d && o.callbacks.onBeforeUpdate && typeof o.callbacks.onBeforeUpdate==="function"){o.callbacks.onBeforeUpdate.call(this);} /* callbacks: onBeforeUpdate */ - - /* if element was disabled or destroyed, remove class(es) */ - if($this.hasClass(classes[3])){$this.removeClass(classes[3]);} - if($this.hasClass(classes[4])){$this.removeClass(classes[4]);} - - /* css flexbox fix, detect/set max-height */ - mCustomScrollBox.css("max-height","none"); - if(mCustomScrollBox.height()!==$this.height()){mCustomScrollBox.css("max-height",$this.height());} - - _expandContentHorizontally.call(this); /* expand content horizontally */ - - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - - d.overflowed=_overflowed.call(this); /* determine if scrolling is required */ - - _scrollbarVisibility.call(this); /* show/hide scrollbar(s) */ - - /* auto-adjust scrollbar dragger length analogous to content */ - if(o.autoDraggerLength){_setDraggerLength.call(this);} - - _scrollRatio.call(this); /* calculate and store scrollbar to content ratio */ - - _bindEvents.call(this); /* bind scrollbar events */ - - /* reset scrolling position and/or events */ - var to=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)]; - if(o.axis!=="x"){ /* y/yx axis */ - if(!d.overflowed[0]){ /* y scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="y"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[1]){ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[0].height()>mCSB_dragger[0].parent().height()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* y scrolling is required */ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - d.contentReset.y=null; - } - } - if(o.axis!=="y"){ /* x/yx axis */ - if(!d.overflowed[1]){ /* x scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="x"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[0]){ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[1].width()>mCSB_dragger[1].parent().width()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* x scrolling is required */ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - d.contentReset.x=null; - } - } - - /* callbacks: onImageLoad, onSelectorChange, onUpdate */ - if(cb && d){ - if(cb===2 && o.callbacks.onImageLoad && typeof o.callbacks.onImageLoad==="function"){ - o.callbacks.onImageLoad.call(this); - }else if(cb===3 && o.callbacks.onSelectorChange && typeof o.callbacks.onSelectorChange==="function"){ - o.callbacks.onSelectorChange.call(this); - }else if(o.callbacks.onUpdate && typeof o.callbacks.onUpdate==="function"){ - o.callbacks.onUpdate.call(this); - } - } - - _autoUpdate.call(this); /* initialize automatic updating (for dynamic content, fluid layouts etc.) */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin scrollTo method - triggers a scrolling event to a specific value - ---------------------------------------- - usage: $(selector).mCustomScrollbar("scrollTo",value,options); - */ - - scrollTo:function(val,options){ - - /* prevent silly things like $(selector).mCustomScrollbar("scrollTo",undefined); */ - if(typeof val=="undefined" || val==null){return;} - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - /* method default options */ - methodDefaults={ - trigger:"external", /* method is by default triggered externally (e.g. from other scripts) */ - scrollInertia:o.scrollInertia, /* scrolling inertia (animation duration) */ - scrollEasing:"mcsEaseInOut", /* animation easing */ - moveDragger:false, /* move dragger instead of content */ - timeout:60, /* scroll-to delay */ - callbacks:true, /* enable/disable callbacks */ - onStart:true, - onUpdate:true, - onComplete:true - }, - methodOptions=$.extend(true,{},methodDefaults,options), - to=_arr.call(this,val),dur=methodOptions.scrollInertia>0 && methodOptions.scrollInertia<17 ? 17 : methodOptions.scrollInertia; - - /* translate yx values to actual scroll-to positions */ - to[0]=_to.call(this,to[0],"y"); - to[1]=_to.call(this,to[1],"x"); - - /* - check if scroll-to value moves the dragger instead of content. - Only pixel values apply on dragger (e.g. 100, "100px", "-=100" etc.) - */ - if(methodOptions.moveDragger){ - to[0]*=d.scrollRatio.y; - to[1]*=d.scrollRatio.x; - } - - methodOptions.dur=_isTabHidden() ? 0 : dur; //skip animations if browser tab is hidden - - setTimeout(function(){ - /* do the scrolling */ - if(to[0]!==null && typeof to[0]!=="undefined" && o.axis!=="x" && d.overflowed[0]){ /* scroll y */ - methodOptions.dir="y"; - methodOptions.overwrite="all"; - _scrollTo($this,to[0].toString(),methodOptions); - } - if(to[1]!==null && typeof to[1]!=="undefined" && o.axis!=="y" && d.overflowed[1]){ /* scroll x */ - methodOptions.dir="x"; - methodOptions.overwrite="none"; - _scrollTo($this,to[1].toString(),methodOptions); - } - },methodOptions.timeout); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin stop method - stops scrolling animation - ---------------------------------------- - usage: $(selector).mCustomScrollbar("stop"); - */ - stop:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - _stop($this); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin disable method - temporarily disables the scrollbar(s) - ---------------------------------------- - usage: $(selector).mCustomScrollbar("disable",reset); - reset (boolean): resets content position to 0 - */ - disable:function(r){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx); - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - if(r){_resetContentPosition.call(this);} /* reset content position */ - - _scrollbarVisibility.call(this,true); /* show/hide scrollbar(s) */ - - $this.addClass(classes[3]); /* add disable class */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin destroy method - completely removes the scrollbar(s) and returns the element to its original state - ---------------------------------------- - usage: $(selector).mCustomScrollbar("destroy"); - */ - destroy:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - scrollbar=$(".mCSB_"+d.idx+"_scrollbar"); - - if(o.live){removeLiveTimers(o.liveSelector || $(selector).selector);} /* remove live timers */ - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - _resetContentPosition.call(this); /* reset content position */ - - $this.removeData(pluginPfx); /* remove plugin data object */ - - _delete(this,"mcs"); /* delete callbacks object */ - - /* remove plugin markup */ - scrollbar.remove(); /* remove scrollbar(s) first (those can be either inside or outside plugin's inner wrapper) */ - mCSB_container.find("img."+classes[2]).removeClass(classes[2]); /* remove loaded images flag */ - mCustomScrollBox.replaceWith(mCSB_container.contents()); /* replace plugin's inner wrapper with the original content */ - /* remove plugin classes from the element and add destroy class */ - $this.removeClass(pluginNS+" _"+pluginPfx+"_"+d.idx+" "+classes[6]+" "+classes[7]+" "+classes[5]+" "+classes[3]).addClass(classes[4]); - - } - - }); - - } - /* ---------------------------------------- */ - - }, - - - - - - /* - ---------------------------------------- - FUNCTIONS - ---------------------------------------- - */ - - /* validates selector (if selector is invalid or undefined uses the default one) */ - _selector=function(){ - return (typeof $(this)!=="object" || $(this).length<1) ? defaultSelector : this; - }, - /* -------------------- */ - - - /* changes options according to theme */ - _theme=function(obj){ - var fixedSizeScrollbarThemes=["rounded","rounded-dark","rounded-dots","rounded-dots-dark"], - nonExpandedScrollbarThemes=["rounded-dots","rounded-dots-dark","3d","3d-dark","3d-thick","3d-thick-dark","inset","inset-dark","inset-2","inset-2-dark","inset-3","inset-3-dark"], - disabledScrollButtonsThemes=["minimal","minimal-dark"], - enabledAutoHideScrollbarThemes=["minimal","minimal-dark"], - scrollbarPositionOutsideThemes=["minimal","minimal-dark"]; - obj.autoDraggerLength=$.inArray(obj.theme,fixedSizeScrollbarThemes) > -1 ? false : obj.autoDraggerLength; - obj.autoExpandScrollbar=$.inArray(obj.theme,nonExpandedScrollbarThemes) > -1 ? false : obj.autoExpandScrollbar; - obj.scrollButtons.enable=$.inArray(obj.theme,disabledScrollButtonsThemes) > -1 ? false : obj.scrollButtons.enable; - obj.autoHideScrollbar=$.inArray(obj.theme,enabledAutoHideScrollbarThemes) > -1 ? true : obj.autoHideScrollbar; - obj.scrollbarPosition=$.inArray(obj.theme,scrollbarPositionOutsideThemes) > -1 ? "outside" : obj.scrollbarPosition; - }, - /* -------------------- */ - - - /* live option timers removal */ - removeLiveTimers=function(selector){ - if(liveTimers[selector]){ - clearTimeout(liveTimers[selector]); - _delete(liveTimers,selector); - } - }, - /* -------------------- */ - - - /* normalizes axis option to valid values: "y", "x", "yx" */ - _findAxis=function(val){ - return (val==="yx" || val==="xy" || val==="auto") ? "yx" : (val==="x" || val==="horizontal") ? "x" : "y"; - }, - /* -------------------- */ - - - /* normalizes scrollButtons.scrollType option to valid values: "stepless", "stepped" */ - _findScrollButtonsType=function(val){ - return (val==="stepped" || val==="pixels" || val==="step" || val==="click") ? "stepped" : "stepless"; - }, - /* -------------------- */ - - - /* generates plugin markup */ - _pluginMarkup=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - expandClass=o.autoExpandScrollbar ? " "+classes[1]+"_expand" : "", - scrollbar=["
    ","
    "], - wrapperClass=o.axis==="yx" ? "mCSB_vertical_horizontal" : o.axis==="x" ? "mCSB_horizontal" : "mCSB_vertical", - scrollbars=o.axis==="yx" ? scrollbar[0]+scrollbar[1] : o.axis==="x" ? scrollbar[1] : scrollbar[0], - contentWrapper=o.axis==="yx" ? "
    " : "", - autoHideClass=o.autoHideScrollbar ? " "+classes[6] : "", - scrollbarDirClass=(o.axis!=="x" && d.langDir==="rtl") ? " "+classes[7] : ""; - if(o.setWidth){$this.css("width",o.setWidth);} /* set element width */ - if(o.setHeight){$this.css("height",o.setHeight);} /* set element height */ - o.setLeft=(o.axis!=="y" && d.langDir==="rtl") ? "989999px" : o.setLeft; /* adjust left position for rtl direction */ - $this.addClass(pluginNS+" _"+pluginPfx+"_"+d.idx+autoHideClass+scrollbarDirClass).wrapInner("
    "); - var mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - if(o.scrollbarPosition==="outside"){ - if($this.css("position")==="static"){ /* requires elements with non-static position */ - $this.css("position","relative"); - } - $this.css("overflow","visible"); - mCustomScrollBox.addClass("mCSB_outside").after(scrollbars); - }else{ - mCustomScrollBox.addClass("mCSB_inside").append(scrollbars); - mCSB_container.wrap(contentWrapper); - } - _scrollButtons.call(this); /* add scrollbar buttons */ - /* minimum dragger length */ - var mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - mCSB_dragger[0].css("min-height",mCSB_dragger[0].height()); - mCSB_dragger[1].css("min-width",mCSB_dragger[1].width()); - }, - /* -------------------- */ - - - /* calculates content width */ - _contentWidth=function(el){ - var val=[el[0].scrollWidth,Math.max.apply(Math,el.children().map(function(){return $(this).outerWidth(true);}).get())],w=el.parent().width(); - return val[0]>w ? val[0] : val[1]>w ? val[1] : "100%"; - }, - /* -------------------- */ - - - /* expands content horizontally */ - _expandContentHorizontally=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.autoExpandHorizontalScroll && o.axis!=="y"){ - /* calculate scrollWidth */ - mCSB_container.css({"width":"auto","min-width":0,"overflow-x":"scroll"}); - var w=Math.ceil(mCSB_container[0].scrollWidth); - if(o.advanced.autoExpandHorizontalScroll===3 || (o.advanced.autoExpandHorizontalScroll!==2 && w>mCSB_container.parent().width())){ - mCSB_container.css({"width":w,"min-width":"100%","overflow-x":"inherit"}); - }else{ - /* - wrap content with an infinite width div and set its position to absolute and width to auto. - Setting width to auto before calculating the actual width is important! - We must let the browser set the width as browser zoom values are impossible to calculate. - */ - mCSB_container.css({"overflow-x":"inherit","position":"absolute"}) - .wrap("
    ") - .css({ /* set actual width, original position and un-wrap */ - /* - get the exact width (with decimals) and then round-up. - Using jquery outerWidth() will round the width value which will mess up with inner elements that have non-integer width - */ - "width":(Math.ceil(mCSB_container[0].getBoundingClientRect().right+0.4)-Math.floor(mCSB_container[0].getBoundingClientRect().left)), - "min-width":"100%", - "position":"relative" - }).unwrap(); - } - } - }, - /* -------------------- */ - - - /* adds scrollbar buttons */ - _scrollButtons=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_scrollTools=$(".mCSB_"+d.idx+"_scrollbar:first"), - tabindex=!_isNumeric(o.scrollButtons.tabindex) ? "" : "tabindex='"+o.scrollButtons.tabindex+"'", - btnHTML=[ - "", - "", - "", - "" - ], - btn=[(o.axis==="x" ? btnHTML[2] : btnHTML[0]),(o.axis==="x" ? btnHTML[3] : btnHTML[1]),btnHTML[2],btnHTML[3]]; - if(o.scrollButtons.enable){ - mCSB_scrollTools.prepend(btn[0]).append(btn[1]).next(".mCSB_scrollTools").prepend(btn[2]).append(btn[3]); - } - }, - /* -------------------- */ - - - /* auto-adjusts scrollbar dragger length */ - _setDraggerLength=function(){ - var $this=$(this),d=$this.data(pluginPfx), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - ratio=[mCustomScrollBox.height()/mCSB_container.outerHeight(false),mCustomScrollBox.width()/mCSB_container.outerWidth(false)], - l=[ - parseInt(mCSB_dragger[0].css("min-height")),Math.round(ratio[0]*mCSB_dragger[0].parent().height()), - parseInt(mCSB_dragger[1].css("min-width")),Math.round(ratio[1]*mCSB_dragger[1].parent().width()) - ], - h=oldIE && (l[1]contentHeight){contentHeight=h;} - if(w>contentWidth){contentWidth=w;} - return [contentHeight>mCustomScrollBox.height(),contentWidth>mCustomScrollBox.width()]; - }, - /* -------------------- */ - - - /* resets content position to 0 */ - _resetContentPosition=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - _stop($this); /* stop any current scrolling before resetting */ - if((o.axis!=="x" && !d.overflowed[0]) || (o.axis==="y" && d.overflowed[0])){ /* reset y */ - mCSB_dragger[0].add(mCSB_container).css("top",0); - _scrollTo($this,"_resetY"); - } - if((o.axis!=="y" && !d.overflowed[1]) || (o.axis==="x" && d.overflowed[1])){ /* reset x */ - var cx=dx=0; - if(d.langDir==="rtl"){ /* adjust left position for rtl direction */ - cx=mCustomScrollBox.width()-mCSB_container.outerWidth(false); - dx=Math.abs(cx/d.scrollRatio.x); - } - mCSB_container.css("left",cx); - mCSB_dragger[1].css("left",dx); - _scrollTo($this,"_resetX"); - } - }, - /* -------------------- */ - - - /* binds scrollbar events */ - _bindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt; - if(!d.bindEvents){ /* check if events are already bound */ - _draggable.call(this); - if(o.contentTouchScroll){_contentDraggable.call(this);} - _selectable.call(this); - if(o.mouseWheel.enable){ /* bind mousewheel fn when plugin is available */ - function _mwt(){ - mousewheelTimeout=setTimeout(function(){ - if(!$.event.special.mousewheel){ - _mwt(); - }else{ - clearTimeout(mousewheelTimeout); - _mousewheel.call($this[0]); - } - },100); - } - var mousewheelTimeout; - _mwt(); - } - _draggerRail.call(this); - _wrapperScroll.call(this); - if(o.advanced.autoScrollOnFocus){_focus.call(this);} - if(o.scrollButtons.enable){_buttons.call(this);} - if(o.keyboard.enable){_keyboard.call(this);} - d.bindEvents=true; - } - }, - /* -------------------- */ - - - /* unbinds scrollbar events */ - _unbindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - sb=".mCSB_"+d.idx+"_scrollbar", - sel=$("#mCSB_"+d.idx+",#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,"+sb+" ."+classes[12]+",#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal,"+sb+">a"), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.releaseDraggableSelectors){sel.add($(o.advanced.releaseDraggableSelectors));} - if(o.advanced.extraDraggableSelectors){sel.add($(o.advanced.extraDraggableSelectors));} - if(d.bindEvents){ /* check if events are bound */ - /* unbind namespaced events from document/selectors */ - $(document).add($(!_canAccessIFrame() || top.document)).unbind("."+namespace); - sel.each(function(){ - $(this).unbind("."+namespace); - }); - /* clear and delete timeouts/objects */ - clearTimeout($this[0]._focusTimeout); _delete($this[0],"_focusTimeout"); - clearTimeout(d.sequential.step); _delete(d.sequential,"step"); - clearTimeout(mCSB_container[0].onCompleteTimeout); _delete(mCSB_container[0],"onCompleteTimeout"); - d.bindEvents=false; - } - }, - /* -------------------- */ - - - /* toggles scrollbar visibility */ - _scrollbarVisibility=function(disabled){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - contentWrapper=$("#mCSB_"+d.idx+"_container_wrapper"), - content=contentWrapper.length ? contentWrapper : $("#mCSB_"+d.idx+"_container"), - scrollbar=[$("#mCSB_"+d.idx+"_scrollbar_vertical"),$("#mCSB_"+d.idx+"_scrollbar_horizontal")], - mCSB_dragger=[scrollbar[0].find(".mCSB_dragger"),scrollbar[1].find(".mCSB_dragger")]; - if(o.axis!=="x"){ - if(d.overflowed[0] && !disabled){ - scrollbar[0].add(mCSB_dragger[0]).add(scrollbar[0].children("a")).css("display","block"); - content.removeClass(classes[8]+" "+classes[10]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[0].css("display","none");} - content.removeClass(classes[10]); - }else{ - scrollbar[0].css("display","none"); - content.addClass(classes[10]); - } - content.addClass(classes[8]); - } - } - if(o.axis!=="y"){ - if(d.overflowed[1] && !disabled){ - scrollbar[1].add(mCSB_dragger[1]).add(scrollbar[1].children("a")).css("display","block"); - content.removeClass(classes[9]+" "+classes[11]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[1].css("display","none");} - content.removeClass(classes[11]); - }else{ - scrollbar[1].css("display","none"); - content.addClass(classes[11]); - } - content.addClass(classes[9]); - } - } - if(!d.overflowed[0] && !d.overflowed[1]){ - $this.addClass(classes[5]); - }else{ - $this.removeClass(classes[5]); - } - }, - /* -------------------- */ - - - /* returns input coordinates of pointer, touch and mouse events (relative to document) */ - _coordinates=function(e){ - var t=e.type,o=e.target.ownerDocument!==document ? [$(frameElement).offset().top,$(frameElement).offset().left] : null, - io=_canAccessIFrame() && e.target.ownerDocument!==top.document ? [$(e.view.frameElement).offset().top,$(e.view.frameElement).offset().left] : [0,0]; - switch(t){ - case "pointerdown": case "MSPointerDown": case "pointermove": case "MSPointerMove": case "pointerup": case "MSPointerUp": - return o ? [e.originalEvent.pageY-o[0]+io[0],e.originalEvent.pageX-o[1]+io[1],false] : [e.originalEvent.pageY,e.originalEvent.pageX,false]; - break; - case "touchstart": case "touchmove": case "touchend": - var touch=e.originalEvent.touches[0] || e.originalEvent.changedTouches[0], - touches=e.originalEvent.touches.length || e.originalEvent.changedTouches.length; - return e.target.ownerDocument!==document ? [touch.screenY,touch.screenX,touches>1] : [touch.pageY,touch.pageX,touches>1]; - break; - default: - return o ? [e.pageY-o[0]+io[0],e.pageX-o[1]+io[1],false] : [e.pageY,e.pageX,false]; - } - }, - /* -------------------- */ - - - /* - SCROLLBAR DRAG EVENTS - scrolls content via scrollbar dragging - */ - _draggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - draggerId=["mCSB_"+d.idx+"_dragger_vertical","mCSB_"+d.idx+"_dragger_horizontal"], - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=$("#"+draggerId[0]+",#"+draggerId[1]), - draggable,dragY,dragX, - rds=o.advanced.releaseDraggableSelectors ? mCSB_dragger.add($(o.advanced.releaseDraggableSelectors)) : mCSB_dragger, - eds=o.advanced.extraDraggableSelectors ? $(!_canAccessIFrame() || top.document).add($(o.advanced.extraDraggableSelectors)) : $(!_canAccessIFrame() || top.document); - mCSB_dragger.bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - touchActive=true; - if(oldIE){document.onselectstart=function(){return false;}} /* disable text selection for IE < 9 */ - _iframe(false); /* enable scrollbar dragging over iframes by disabling their events */ - _stop($this); - draggable=$(this); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - h=draggable.height()+offset.top,w=draggable.width()+offset.left; - if(y0 && x0){ - dragY=y; - dragX=x; - } - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - }).bind("touchmove."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - _drag(dragY,dragX,y,x); - }); - $(document).add(eds).bind("mousemove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace,function(e){ - if(draggable){ - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - if(dragY===y && dragX===x){return;} /* has it really moved? */ - _drag(dragY,dragX,y,x); - } - }).add(rds).bind("mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - if(draggable){ - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - draggable=null; - } - touchActive=false; - if(oldIE){document.onselectstart=null;} /* enable text selection for IE < 9 */ - _iframe(true); /* enable iframes events */ - }); - function _iframe(evt){ - var el=mCSB_container.find("iframe"); - if(!el.length){return;} /* check if content contains iframes */ - var val=!evt ? "none" : "auto"; - el.css("pointer-events",val); /* for IE11, iframe's display property should not be "block" */ - } - function _drag(dragY,dragX,y,x){ - mCSB_container[0].idleTimer=o.scrollInertia<233 ? 250 : 0; - if(draggable.attr("id")===draggerId[1]){ - var dir="x",to=((draggable[0].offsetLeft-dragX)+x)*d.scrollRatio.x; - }else{ - var dir="y",to=((draggable[0].offsetTop-dragY)+y)*d.scrollRatio.y; - } - _scrollTo($this,to.toString(),{dir:dir,drag:true}); - } - }, - /* -------------------- */ - - - /* - TOUCH SWIPE EVENTS - scrolls content via touch swipe - Emulates the native touch-swipe scrolling with momentum found in iOS, Android and WP devices - */ - _contentDraggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - draggable,dragY,dragX,touchStartY,touchStartX,touchMoveY=[],touchMoveX=[],startTime,runningTime,endTime,distance,speed,amount, - durA=0,durB,overwrite=o.axis==="yx" ? "none" : "all",touchIntent=[],touchDrag,docDrag, - iframe=mCSB_container.find("iframe"), - events=[ - "touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace, //start - "touchmove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace, //move - "touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace //end - ], - touchAction=document.body.style.touchAction!==undefined; - mCSB_container.bind(events[0],function(e){ - _onTouchstart(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }); - mCustomScrollBox.bind(events[0],function(e){ - _onTouchstart2(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - if(iframe.length){ - iframe.each(function(){ - $(this).load(function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onTouchstart(e); - _onTouchstart2(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - } - }); - }); - } - function _onTouchstart(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){touchable=0; return;} - touchable=1; touchDrag=0; docDrag=0; draggable=1; - $this.removeClass("mCS_touch_action"); - var offset=mCSB_container.offset(); - dragY=_coordinates(e)[0]-offset.top; - dragX=_coordinates(e)[1]-offset.left; - touchIntent=[_coordinates(e)[0],_coordinates(e)[1]]; - } - function _onTouchmove(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){return;} - if(!o.documentTouchScroll){e.preventDefault();} - e.stopImmediatePropagation(); - if(docDrag && !touchDrag){return;} - if(draggable){ - runningTime=_getTime(); - var offset=mCustomScrollBox.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - easing="mcsLinearOut"; - touchMoveY.push(y); - touchMoveX.push(x); - touchIntent[2]=Math.abs(_coordinates(e)[0]-touchIntent[0]); touchIntent[3]=Math.abs(_coordinates(e)[1]-touchIntent[1]); - if(d.overflowed[0]){ - var limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - prevent=((dragY-y)>0 && (y-dragY)>-(limit*d.scrollRatio.y) && (touchIntent[3]*20 && (x-dragX)>-(limitX*d.scrollRatio.x) && (touchIntent[2]*230){return;} - speed=1000/(endTime-startTime); - var easing="mcsEaseOut",slow=speed<2.5, - diff=slow ? [touchMoveY[touchMoveY.length-2],touchMoveX[touchMoveX.length-2]] : [0,0]; - distance=slow ? [(y-diff[0]),(x-diff[1])] : [y-touchStartY,x-touchStartX]; - var absDistance=[Math.abs(distance[0]),Math.abs(distance[1])]; - speed=slow ? [Math.abs(distance[0]/4),Math.abs(distance[1]/4)] : [speed,speed]; - var a=[ - Math.abs(mCSB_container[0].offsetTop)-(distance[0]*_m((absDistance[0]/speed[0]),speed[0])), - Math.abs(mCSB_container[0].offsetLeft)-(distance[1]*_m((absDistance[1]/speed[1]),speed[1])) - ]; - amount=o.axis==="yx" ? [a[0],a[1]] : o.axis==="x" ? [null,a[1]] : [a[0],null]; - durB=[(absDistance[0]*4)+o.scrollInertia,(absDistance[1]*4)+o.scrollInertia]; - var md=parseInt(o.contentTouchScroll) || 0; /* absolute minimum distance required */ - amount[0]=absDistance[0]>md ? amount[0] : 0; - amount[1]=absDistance[1]>md ? amount[1] : 0; - if(d.overflowed[0]){_drag(amount[0],durB[0],easing,"y",overwrite,false);} - if(d.overflowed[1]){_drag(amount[1],durB[1],easing,"x",overwrite,false);} - } - function _m(ds,s){ - var r=[s*1.5,s*2,s/1.5,s/2]; - if(ds>90){ - return s>4 ? r[0] : r[3]; - }else if(ds>60){ - return s>3 ? r[3] : r[2]; - }else if(ds>30){ - return s>8 ? r[1] : s>6 ? r[0] : s>4 ? s : r[2]; - }else{ - return s>8 ? s : r[3]; - } - } - function _drag(amount,dur,easing,dir,overwrite,drag){ - if(!amount){return;} - _scrollTo($this,amount.toString(),{dur:dur,scrollEasing:easing,dir:dir,overwrite:overwrite,drag:drag}); - } - }, - /* -------------------- */ - - - /* - SELECT TEXT EVENTS - scrolls content when text is selected - */ - _selectable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - action; - mCSB_container.bind("mousedown."+namespace,function(e){ - if(touchable){return;} - if(!action){action=1; touchActive=true;} - }).add(document).bind("mousemove."+namespace,function(e){ - if(!touchable && action && _sel()){ - var offset=mCSB_container.offset(), - y=_coordinates(e)[0]-offset.top+mCSB_container[0].offsetTop,x=_coordinates(e)[1]-offset.left+mCSB_container[0].offsetLeft; - if(y>0 && y0 && xwrapper.height()){ - _seq("on",40); - } - } - if(o.axis!=="y" && d.overflowed[1]){ - if(x<0){ - _seq("on",37); - }else if(x>wrapper.width()){ - _seq("on",39); - } - } - } - } - }).bind("mouseup."+namespace+" dragend."+namespace,function(e){ - if(touchable){return;} - if(action){action=0; _seq("off",null);} - touchActive=false; - }); - function _sel(){ - return window.getSelection ? window.getSelection().toString() : - document.selection && document.selection.type!="Control" ? document.selection.createRange().text : 0; - } - function _seq(a,c,s){ - seq.type=s && action ? "stepped" : "stepless"; - seq.scrollAmount=10; - _sequentialScroll($this,a,c,"mcsLinearOut",s ? 60 : null); - } - }, - /* -------------------- */ - - - /* - MOUSE WHEEL EVENT - scrolls content via mouse-wheel - via mouse-wheel plugin (https://github.com/brandonaaron/jquery-mousewheel) - */ - _mousewheel=function(){ - if(!$(this).data(pluginPfx)){return;} /* Check if the scrollbar is ready to use mousewheel events (issue: #185) */ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - iframe=$("#mCSB_"+d.idx+"_container").find("iframe"); - if(iframe.length){ - iframe.each(function(){ - $(this).load(function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - } - }); - }); - } - mCustomScrollBox.bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - function _onMousewheel(e,delta){ - _stop($this); - if(_disableMousewheel($this,e.target)){return;} /* disables mouse-wheel when hovering specific elements */ - var deltaFactor=o.mouseWheel.deltaFactor!=="auto" ? parseInt(o.mouseWheel.deltaFactor) : (oldIE && e.deltaFactor<100) ? 100 : e.deltaFactor || 100, - dur=o.scrollInertia; - if(o.axis==="x" || o.mouseWheel.axis==="x"){ - var dir="x", - px=[Math.round(deltaFactor*d.scrollRatio.x),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.width() ? mCustomScrollBox.width()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetLeft), - draggerPos=mCSB_dragger[1][0].offsetLeft, - limit=mCSB_dragger[1].parent().width()-mCSB_dragger[1].width(), - dlt=e.deltaX || e.deltaY || delta; - }else{ - var dir="y", - px=[Math.round(deltaFactor*d.scrollRatio.y),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.height() ? mCustomScrollBox.height()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetTop), - draggerPos=mCSB_dragger[0][0].offsetTop, - limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - dlt=e.deltaY || delta; - } - if((dir==="y" && !d.overflowed[0]) || (dir==="x" && !d.overflowed[1])){return;} - if(o.mouseWheel.invert || e.webkitDirectionInvertedFromDevice){dlt=-dlt;} - if(o.mouseWheel.normalizeDelta){dlt=dlt<0 ? -1 : 1;} - if((dlt>0 && draggerPos!==0) || (dlt<0 && draggerPos!==limit) || o.mouseWheel.preventDefault){ - e.stopImmediatePropagation(); - e.preventDefault(); - } - if(e.deltaFactor<2 && !o.mouseWheel.normalizeDelta){ - //very low deltaFactor values mean some kind of delta acceleration (e.g. osx trackpad), so adjusting scrolling accordingly - amount=e.deltaFactor; dur=17; - } - _scrollTo($this,(contentPos-(dlt*amount)).toString(),{dir:dir,dur:dur}); - } - }, - /* -------------------- */ - - - /* checks if iframe can be accessed */ - _canAccessIFrame=function(iframe){ - var html=null; - if(!iframe){ - try{ - var doc=top.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - return(html!==null); - }else{ - try{ - var doc=iframe.contentDocument || iframe.contentWindow.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - return(html!==null); - } - }, - /* -------------------- */ - - - /* disables mouse-wheel when hovering specific elements like select, datalist etc. */ - _disableMousewheel=function(el,target){ - var tag=target.nodeName.toLowerCase(), - tags=el.data(pluginPfx).opt.mouseWheel.disableOver, - /* elements that require focus */ - focusTags=["select","textarea"]; - return $.inArray(tag,tags) > -1 && !($.inArray(tag,focusTags) > -1 && !$(target).is(":focus")); - }, - /* -------------------- */ - - - /* - DRAGGER RAIL CLICK EVENT - scrolls content via dragger rail - */ - _draggerRail=function(){ - var $this=$(this),d=$this.data(pluginPfx), - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - mCSB_draggerContainer=$(".mCSB_"+d.idx+"_scrollbar ."+classes[12]), - clickable; - mCSB_draggerContainer.bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - touchActive=true; - if(!$(e.target).hasClass("mCSB_dragger")){clickable=1;} - }).bind("touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - touchActive=false; - }).bind("click."+namespace,function(e){ - if(!clickable){return;} - clickable=0; - if($(e.target).hasClass(classes[12]) || $(e.target).hasClass("mCSB_draggerRail")){ - _stop($this); - var el=$(this),mCSB_dragger=el.find(".mCSB_dragger"); - if(el.parent(".mCSB_scrollTools_horizontal").length>0){ - if(!d.overflowed[1]){return;} - var dir="x", - clickDir=e.pageX>mCSB_dragger.offset().left ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetLeft)-(clickDir*(wrapper.width()*0.9)); - }else{ - if(!d.overflowed[0]){return;} - var dir="y", - clickDir=e.pageY>mCSB_dragger.offset().top ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetTop)-(clickDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }); - }, - /* -------------------- */ - - - /* - FOCUS EVENT - scrolls content via element focus (e.g. clicking an input, pressing TAB key etc.) - */ - _focus=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(); - mCSB_container.bind("focusin."+namespace,function(e){ - var el=$(document.activeElement), - nested=mCSB_container.find(".mCustomScrollBox").length, - dur=0; - if(!el.is(o.advanced.autoScrollOnFocus)){return;} - _stop($this); - clearTimeout($this[0]._focusTimeout); - $this[0]._focusTimer=nested ? (dur+17)*nested : 0; - $this[0]._focusTimeout=setTimeout(function(){ - var to=[_childPos(el)[0],_childPos(el)[1]], - contentPos=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], - isVisible=[ - (contentPos[0]+to[0]>=0 && contentPos[0]+to[0]=0 && contentPos[0]+to[1]a"); - btn.bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace+" mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace+" mouseout."+namespace+" pointerout."+namespace+" MSPointerOut."+namespace+" click."+namespace,function(e){ - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - var btnClass=$(this).attr("class"); - seq.type=o.scrollButtons.scrollType; - switch(e.type){ - case "mousedown": case "touchstart": case "pointerdown": case "MSPointerDown": - if(seq.type==="stepped"){return;} - touchActive=true; - d.tweenRunning=false; - _seq("on",btnClass); - break; - case "mouseup": case "touchend": case "pointerup": case "MSPointerUp": - case "mouseout": case "pointerout": case "MSPointerOut": - if(seq.type==="stepped"){return;} - touchActive=false; - if(seq.dir){_seq("off",btnClass);} - break; - case "click": - if(seq.type!=="stepped" || d.tweenRunning){return;} - _seq("on",btnClass); - break; - } - function _seq(a,c){ - seq.scrollAmount=o.scrollButtons.scrollAmount; - _sequentialScroll($this,a,c); - } - }); - }, - /* -------------------- */ - - - /* - KEYBOARD EVENTS - scrolls content via keyboard - Keys: up arrow, down arrow, left arrow, right arrow, PgUp, PgDn, Home, End - */ - _keyboard=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - editables="input,textarea,select,datalist,keygen,[contenteditable='true']", - iframe=mCSB_container.find("iframe"), - events=["blur."+namespace+" keydown."+namespace+" keyup."+namespace]; - if(iframe.length){ - iframe.each(function(){ - $(this).load(function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onKeyboard(e); - }); - } - }); - }); - } - mCustomScrollBox.attr("tabindex","0").bind(events[0],function(e){ - _onKeyboard(e); - }); - function _onKeyboard(e){ - switch(e.type){ - case "blur": - if(d.tweenRunning && seq.dir){_seq("off",null);} - break; - case "keydown": case "keyup": - var code=e.keyCode ? e.keyCode : e.which,action="on"; - if((o.axis!=="x" && (code===38 || code===40)) || (o.axis!=="y" && (code===37 || code===39))){ - /* up (38), down (40), left (37), right (39) arrows */ - if(((code===38 || code===40) && !d.overflowed[0]) || ((code===37 || code===39) && !d.overflowed[1])){return;} - if(e.type==="keyup"){action="off";} - if(!$(document.activeElement).is(editables)){ - e.preventDefault(); - e.stopImmediatePropagation(); - _seq(action,code); - } - }else if(code===33 || code===34){ - /* PgUp (33), PgDn (34) */ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - _stop($this); - var keyboardDir=code===34 ? -1 : 1; - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=Math.abs(mCSB_container[0].offsetLeft)-(keyboardDir*(wrapper.width()*0.9)); - }else{ - var dir="y",to=Math.abs(mCSB_container[0].offsetTop)-(keyboardDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }else if(code===35 || code===36){ - /* End (35), Home (36) */ - if(!$(document.activeElement).is(editables)){ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=code===35 ? Math.abs(wrapper.width()-mCSB_container.outerWidth(false)) : 0; - }else{ - var dir="y",to=code===35 ? Math.abs(wrapper.height()-mCSB_container.outerHeight(false)) : 0; - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - } - } - break; - } - function _seq(a,c){ - seq.type=o.keyboard.scrollType; - seq.scrollAmount=o.keyboard.scrollAmount; - if(seq.type==="stepped" && d.tweenRunning){return;} - _sequentialScroll($this,a,c); - } - } - }, - /* -------------------- */ - - - /* scrolls content sequentially (used when scrolling via buttons, keyboard arrows etc.) */ - _sequentialScroll=function(el,action,trigger,e,s){ - var d=el.data(pluginPfx),o=d.opt,seq=d.sequential, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - once=seq.type==="stepped" ? true : false, - steplessSpeed=o.scrollInertia < 26 ? 26 : o.scrollInertia, /* 26/1.5=17 */ - steppedSpeed=o.scrollInertia < 1 ? 17 : o.scrollInertia; - switch(action){ - case "on": - seq.dir=[ - (trigger===classes[16] || trigger===classes[15] || trigger===39 || trigger===37 ? "x" : "y"), - (trigger===classes[13] || trigger===classes[15] || trigger===38 || trigger===37 ? -1 : 1) - ]; - _stop(el); - if(_isNumeric(trigger) && seq.type==="stepped"){return;} - _on(once); - break; - case "off": - _off(); - if(once || (d.tweenRunning && seq.dir)){ - _on(true); - } - break; - } - - /* starts sequence */ - function _on(once){ - if(o.snapAmount){seq.scrollAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : seq.dir[0]==="x" ? o.snapAmount[1] : o.snapAmount[0];} /* scrolling snapping */ - var c=seq.type!=="stepped", /* continuous scrolling */ - t=s ? s : !once ? 1000/60 : c ? steplessSpeed/1.5 : steppedSpeed, /* timer */ - m=!once ? 2.5 : c ? 7.5 : 40, /* multiplier */ - contentPos=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)], - ratio=[d.scrollRatio.y>10 ? 10 : d.scrollRatio.y,d.scrollRatio.x>10 ? 10 : d.scrollRatio.x], - amount=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*(ratio[1]*m)) : contentPos[0]+(seq.dir[1]*(ratio[0]*m)), - px=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*parseInt(seq.scrollAmount)) : contentPos[0]+(seq.dir[1]*parseInt(seq.scrollAmount)), - to=seq.scrollAmount!=="auto" ? px : amount, - easing=e ? e : !once ? "mcsLinear" : c ? "mcsLinearOut" : "mcsEaseInOut", - onComplete=!once ? false : true; - if(once && t<17){ - to=seq.dir[0]==="x" ? contentPos[1] : contentPos[0]; - } - _scrollTo(el,to.toString(),{dir:seq.dir[0],scrollEasing:easing,dur:t,onComplete:onComplete}); - if(once){ - seq.dir=false; - return; - } - clearTimeout(seq.step); - seq.step=setTimeout(function(){ - _on(); - },t); - } - /* stops sequence */ - function _off(){ - clearTimeout(seq.step); - _delete(seq,"step"); - _stop(el); - } - }, - /* -------------------- */ - - - /* returns a yx array from value */ - _arr=function(val){ - var o=$(this).data(pluginPfx).opt,vals=[]; - if(typeof val==="function"){val=val();} /* check if the value is a single anonymous function */ - /* check if value is object or array, its length and create an array with yx values */ - if(!(val instanceof Array)){ /* object value (e.g. {y:"100",x:"100"}, 100 etc.) */ - vals[0]=val.y ? val.y : val.x || o.axis==="x" ? null : val; - vals[1]=val.x ? val.x : val.y || o.axis==="y" ? null : val; - }else{ /* array value (e.g. [100,100]) */ - vals=val.length>1 ? [val[0],val[1]] : o.axis==="x" ? [null,val[0]] : [val[0],null]; - } - /* check if array values are anonymous functions */ - if(typeof vals[0]==="function"){vals[0]=vals[0]();} - if(typeof vals[1]==="function"){vals[1]=vals[1]();} - return vals; - }, - /* -------------------- */ - - - /* translates values (e.g. "top", 100, "100px", "#id") to actual scroll-to positions */ - _to=function(val,dir){ - if(val==null || typeof val=="undefined"){return;} - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - t=typeof val; - if(!dir){dir=o.axis==="x" ? "x" : "y";} - var contentLength=dir==="x" ? mCSB_container.outerWidth(false) : mCSB_container.outerHeight(false), - contentPos=dir==="x" ? mCSB_container[0].offsetLeft : mCSB_container[0].offsetTop, - cssProp=dir==="x" ? "left" : "top"; - switch(t){ - case "function": /* this currently is not used. Consider removing it */ - return val(); - break; - case "object": /* js/jquery object */ - var obj=val.jquery ? val : $(val); - if(!obj.length){return;} - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - break; - case "string": case "number": - if(_isNumeric(val)){ /* numeric value */ - return Math.abs(val); - }else if(val.indexOf("%")!==-1){ /* percentage value */ - return Math.abs(contentLength*parseInt(val)/100); - }else if(val.indexOf("-=")!==-1){ /* decrease value */ - return Math.abs(contentPos-parseInt(val.split("-=")[1])); - }else if(val.indexOf("+=")!==-1){ /* inrease value */ - var p=(contentPos+parseInt(val.split("+=")[1])); - return p>=0 ? 0 : Math.abs(p); - }else if(val.indexOf("px")!==-1 && _isNumeric(val.split("px")[0])){ /* pixels string value (e.g. "100px") */ - return Math.abs(val.split("px")[0]); - }else{ - if(val==="top" || val==="left"){ /* special strings */ - return 0; - }else if(val==="bottom"){ - return Math.abs(wrapper.height()-mCSB_container.outerHeight(false)); - }else if(val==="right"){ - return Math.abs(wrapper.width()-mCSB_container.outerWidth(false)); - }else if(val==="first" || val==="last"){ - var obj=mCSB_container.find(":"+val); - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - }else{ - if($(val).length){ /* jquery selector */ - return dir==="x" ? _childPos($(val))[1] : _childPos($(val))[0]; - }else{ /* other values (e.g. "100em") */ - mCSB_container.css(cssProp,val); - methods.update.call(null,$this[0]); - return; - } - } - } - break; - } - }, - /* -------------------- */ - - - /* calls the update method automatically */ - _autoUpdate=function(rem){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(rem){ - /* - removes autoUpdate timer - usage: _autoUpdate.call(this,"remove"); - */ - clearTimeout(mCSB_container[0].autoUpdate); - _delete(mCSB_container[0],"autoUpdate"); - return; - } - upd(); - function upd(){ - clearTimeout(mCSB_container[0].autoUpdate); - if($this.parents("html").length===0){ - /* check element in dom tree */ - $this=null; - return; - } - mCSB_container[0].autoUpdate=setTimeout(function(){ - /* update on specific selector(s) length and size change */ - if(o.advanced.updateOnSelectorChange){ - d.poll.change.n=sizesSum(); - if(d.poll.change.n!==d.poll.change.o){ - d.poll.change.o=d.poll.change.n; - doUpd(3); - return; - } - } - /* update on main element and scrollbar size changes */ - if(o.advanced.updateOnContentResize){ - d.poll.size.n=$this[0].scrollHeight+$this[0].scrollWidth+mCSB_container[0].offsetHeight+$this[0].offsetHeight+$this[0].offsetWidth; - if(d.poll.size.n!==d.poll.size.o){ - d.poll.size.o=d.poll.size.n; - doUpd(1); - return; - } - } - /* update on image load */ - if(o.advanced.updateOnImageLoad){ - if(!(o.advanced.updateOnImageLoad==="auto" && o.axis==="y")){ //by default, it doesn't run on vertical content - d.poll.img.n=mCSB_container.find("img").length; - if(d.poll.img.n!==d.poll.img.o){ - d.poll.img.o=d.poll.img.n; - mCSB_container.find("img").each(function(){ - imgLoader(this); - }); - return; - } - } - } - if(o.advanced.updateOnSelectorChange || o.advanced.updateOnContentResize || o.advanced.updateOnImageLoad){upd();} - },o.advanced.autoUpdateTimeout); - } - /* a tiny image loader */ - function imgLoader(el){ - if($(el).hasClass(classes[2])){doUpd(); return;} - var img=new Image(); - function createDelegate(contextObject,delegateMethod){ - return function(){return delegateMethod.apply(contextObject,arguments);} - } - function imgOnLoad(){ - this.onload=null; - $(el).addClass(classes[2]); - doUpd(2); - } - img.onload=createDelegate(img,imgOnLoad); - img.src=el.src; - } - /* returns the total height and width sum of all elements matching the selector */ - function sizesSum(){ - if(o.advanced.updateOnSelectorChange===true){o.advanced.updateOnSelectorChange="*";} - var total=0,sel=mCSB_container.find(o.advanced.updateOnSelectorChange); - if(o.advanced.updateOnSelectorChange && sel.length>0){sel.each(function(){total+=this.offsetHeight+this.offsetWidth;});} - return total; - } - /* calls the update method */ - function doUpd(cb){ - clearTimeout(mCSB_container[0].autoUpdate); - methods.update.call(null,$this[0],cb); - } - }, - /* -------------------- */ - - - /* snaps scrolling to a multiple of a pixels number */ - _snapAmount=function(to,amount,offset){ - return (Math.round(to/amount)*amount-offset); - }, - /* -------------------- */ - - - /* stops content and scrollbar animations */ - _stop=function(el){ - var d=el.data(pluginPfx), - sel=$("#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal"); - sel.each(function(){ - _stopTween.call(this); - }); - }, - /* -------------------- */ - - - /* - ANIMATES CONTENT - This is where the actual scrolling happens - */ - _scrollTo=function(el,to,options){ - var d=el.data(pluginPfx),o=d.opt, - defaults={ - trigger:"internal", - dir:"y", - scrollEasing:"mcsEaseOut", - drag:false, - dur:o.scrollInertia, - overwrite:"all", - callbacks:true, - onStart:true, - onUpdate:true, - onComplete:true - }, - options=$.extend(defaults,options), - dur=[options.dur,(options.drag ? 0 : options.dur)], - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - totalScrollOffsets=o.callbacks.onTotalScrollOffset ? _arr.call(el,o.callbacks.onTotalScrollOffset) : [0,0], - totalScrollBackOffsets=o.callbacks.onTotalScrollBackOffset ? _arr.call(el,o.callbacks.onTotalScrollBackOffset) : [0,0]; - d.trigger=options.trigger; - if(wrapper.scrollTop()!==0 || wrapper.scrollLeft()!==0){ /* always reset scrollTop/Left */ - $(".mCSB_"+d.idx+"_scrollbar").css("visibility","visible"); - wrapper.scrollTop(0).scrollLeft(0); - } - if(to==="_resetY" && !d.contentReset.y){ - /* callbacks: onOverflowYNone */ - if(_cb("onOverflowYNone")){o.callbacks.onOverflowYNone.call(el[0]);} - d.contentReset.y=1; - } - if(to==="_resetX" && !d.contentReset.x){ - /* callbacks: onOverflowXNone */ - if(_cb("onOverflowXNone")){o.callbacks.onOverflowXNone.call(el[0]);} - d.contentReset.x=1; - } - if(to==="_resetY" || to==="_resetX"){return;} - if((d.contentReset.y || !el[0].mcs) && d.overflowed[0]){ - /* callbacks: onOverflowY */ - if(_cb("onOverflowY")){o.callbacks.onOverflowY.call(el[0]);} - d.contentReset.x=null; - } - if((d.contentReset.x || !el[0].mcs) && d.overflowed[1]){ - /* callbacks: onOverflowX */ - if(_cb("onOverflowX")){o.callbacks.onOverflowX.call(el[0]);} - d.contentReset.x=null; - } - if(o.snapAmount){ /* scrolling snapping */ - var snapAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : options.dir==="x" ? o.snapAmount[1] : o.snapAmount[0]; - to=_snapAmount(to,snapAmount,o.snapOffset); - } - switch(options.dir){ - case "x": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_horizontal"), - property="left", - contentPos=mCSB_container[0].offsetLeft, - limit=[ - mCustomScrollBox.width()-mCSB_container.outerWidth(false), - mCSB_dragger.parent().width()-mCSB_dragger.width() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.x)], - tso=totalScrollOffsets[1], - tsbo=totalScrollBackOffsets[1], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.x : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.x : 0; - break; - case "y": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_vertical"), - property="top", - contentPos=mCSB_container[0].offsetTop, - limit=[ - mCustomScrollBox.height()-mCSB_container.outerHeight(false), - mCSB_dragger.parent().height()-mCSB_dragger.height() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.y)], - tso=totalScrollOffsets[0], - tsbo=totalScrollBackOffsets[0], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.y : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.y : 0; - break; - } - if(scrollTo[1]<0 || (scrollTo[0]===0 && scrollTo[1]===0)){ - scrollTo=[0,0]; - }else if(scrollTo[1]>=limit[1]){ - scrollTo=[limit[0],limit[1]]; - }else{ - scrollTo[0]=-scrollTo[0]; - } - if(!el[0].mcs){ - _mcs(); /* init mcs object (once) to make it available before callbacks */ - if(_cb("onInit")){o.callbacks.onInit.call(el[0]);} /* callbacks: onInit */ - } - clearTimeout(mCSB_container[0].onCompleteTimeout); - _tweenTo(mCSB_dragger[0],property,Math.round(scrollTo[1]),dur[1],options.scrollEasing); - if(!d.tweenRunning && ((contentPos===0 && scrollTo[0]>=0) || (contentPos===limit[0] && scrollTo[0]<=limit[0]))){return;} - _tweenTo(mCSB_container[0],property,Math.round(scrollTo[0]),dur[0],options.scrollEasing,options.overwrite,{ - onStart:function(){ - if(options.callbacks && options.onStart && !d.tweenRunning){ - /* callbacks: onScrollStart */ - if(_cb("onScrollStart")){_mcs(); o.callbacks.onScrollStart.call(el[0]);} - d.tweenRunning=true; - _onDragClasses(mCSB_dragger); - d.cbOffsets=_cbOffsets(); - } - },onUpdate:function(){ - if(options.callbacks && options.onUpdate){ - /* callbacks: whileScrolling */ - if(_cb("whileScrolling")){_mcs(); o.callbacks.whileScrolling.call(el[0]);} - } - },onComplete:function(){ - if(options.callbacks && options.onComplete){ - if(o.axis==="yx"){clearTimeout(mCSB_container[0].onCompleteTimeout);} - var t=mCSB_container[0].idleTimer || 0; - mCSB_container[0].onCompleteTimeout=setTimeout(function(){ - /* callbacks: onScroll, onTotalScroll, onTotalScrollBack */ - if(_cb("onScroll")){_mcs(); o.callbacks.onScroll.call(el[0]);} - if(_cb("onTotalScroll") && scrollTo[1]>=limit[1]-totalScrollOffset && d.cbOffsets[0]){_mcs(); o.callbacks.onTotalScroll.call(el[0]);} - if(_cb("onTotalScrollBack") && scrollTo[1]<=totalScrollBackOffset && d.cbOffsets[1]){_mcs(); o.callbacks.onTotalScrollBack.call(el[0]);} - d.tweenRunning=false; - mCSB_container[0].idleTimer=0; - _onDragClasses(mCSB_dragger,"hide"); - },t); - } - } - }); - /* checks if callback function exists */ - function _cb(cb){ - return d && o.callbacks[cb] && typeof o.callbacks[cb]==="function"; - } - /* checks whether callback offsets always trigger */ - function _cbOffsets(){ - return [o.callbacks.alwaysTriggerOffsets || contentPos>=limit[0]+tso,o.callbacks.alwaysTriggerOffsets || contentPos<=-tsbo]; - } - /* - populates object with useful values for the user - values: - content: this.mcs.content - content top position: this.mcs.top - content left position: this.mcs.left - dragger top position: this.mcs.draggerTop - dragger left position: this.mcs.draggerLeft - scrolling y percentage: this.mcs.topPct - scrolling x percentage: this.mcs.leftPct - scrolling direction: this.mcs.direction - */ - function _mcs(){ - var cp=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], /* content position */ - dp=[mCSB_dragger[0].offsetTop,mCSB_dragger[0].offsetLeft], /* dragger position */ - cl=[mCSB_container.outerHeight(false),mCSB_container.outerWidth(false)], /* content length */ - pl=[mCustomScrollBox.height(),mCustomScrollBox.width()]; /* content parent length */ - el[0].mcs={ - content:mCSB_container, /* original content wrapper as jquery object */ - top:cp[0],left:cp[1],draggerTop:dp[0],draggerLeft:dp[1], - topPct:Math.round((100*Math.abs(cp[0]))/(Math.abs(cl[0])-pl[0])),leftPct:Math.round((100*Math.abs(cp[1]))/(Math.abs(cl[1])-pl[1])), - direction:options.dir - }; - /* - this refers to the original element containing the scrollbar(s) - usage: this.mcs.top, this.mcs.leftPct etc. - */ - } - }, - /* -------------------- */ - - - /* - CUSTOM JAVASCRIPT ANIMATION TWEEN - Lighter and faster than jquery animate() and css transitions - Animates top/left properties and includes easings - */ - _tweenTo=function(el,prop,to,duration,easing,overwrite,callbacks){ - if(!el._mTween){el._mTween={top:{},left:{}};} - var callbacks=callbacks || {}, - onStart=callbacks.onStart || function(){},onUpdate=callbacks.onUpdate || function(){},onComplete=callbacks.onComplete || function(){}, - startTime=_getTime(),_delay,progress=0,from=el.offsetTop,elStyle=el.style,_request,tobj=el._mTween[prop]; - if(prop==="left"){from=el.offsetLeft;} - var diff=to-from; - tobj.stop=0; - if(overwrite!=="none"){_cancelTween();} - _startTween(); - function _step(){ - if(tobj.stop){return;} - if(!progress){onStart.call();} - progress=_getTime()-startTime; - _tween(); - if(progress>=tobj.time){ - tobj.time=(progress>tobj.time) ? progress+_delay-(progress-tobj.time) : progress+_delay-1; - if(tobj.time0){ - tobj.currVal=_ease(tobj.time,from,diff,duration,easing); - elStyle[prop]=Math.round(tobj.currVal)+"px"; - }else{ - elStyle[prop]=to+"px"; - } - onUpdate.call(); - } - function _startTween(){ - _delay=1000/60; - tobj.time=progress+_delay; - _request=(!window.requestAnimationFrame) ? function(f){_tween(); return setTimeout(f,0.01);} : window.requestAnimationFrame; - tobj.id=_request(_step); - } - function _cancelTween(){ - if(tobj.id==null){return;} - if(!window.requestAnimationFrame){clearTimeout(tobj.id); - }else{window.cancelAnimationFrame(tobj.id);} - tobj.id=null; - } - function _ease(t,b,c,d,type){ - switch(type){ - case "linear": case "mcsLinear": - return c*t/d + b; - break; - case "mcsLinearOut": - t/=d; t--; return c * Math.sqrt(1 - t*t) + b; - break; - case "easeInOutSmooth": - t/=d/2; - if(t<1) return c/2*t*t + b; - t--; - return -c/2 * (t*(t-2) - 1) + b; - break; - case "easeInOutStrong": - t/=d/2; - if(t<1) return c/2 * Math.pow( 2, 10 * (t - 1) ) + b; - t--; - return c/2 * ( -Math.pow( 2, -10 * t) + 2 ) + b; - break; - case "easeInOut": case "mcsEaseInOut": - t/=d/2; - if(t<1) return c/2*t*t*t + b; - t-=2; - return c/2*(t*t*t + 2) + b; - break; - case "easeOutSmooth": - t/=d; t--; - return -c * (t*t*t*t - 1) + b; - break; - case "easeOutStrong": - return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b; - break; - case "easeOut": case "mcsEaseOut": default: - var ts=(t/=d)*t,tc=ts*t; - return b+c*(0.499999999999997*tc*ts + -2.5*ts*ts + 5.5*tc + -6.5*ts + 4*t); - } - } - }, - /* -------------------- */ - - - /* returns current time */ - _getTime=function(){ - if(window.performance && window.performance.now){ - return window.performance.now(); - }else{ - if(window.performance && window.performance.webkitNow){ - return window.performance.webkitNow(); - }else{ - if(Date.now){return Date.now();}else{return new Date().getTime();} - } - } - }, - /* -------------------- */ - - - /* stops a tween */ - _stopTween=function(){ - var el=this; - if(!el._mTween){el._mTween={top:{},left:{}};} - var props=["top","left"]; - for(var i=0; i
  • "+$(n).text()+"
  • ")}),i.call(t)):(a.html("
  • "),$(".search-results li").text('No Results Found for "'+t.value+'"'))}else o(),a.removeClass("visible")}function i(){this.value&&s.highlight(this.value,u)}function o(){s.unhighlight(u)}var s,a,u={element:"span",className:"search-highlight"},c=0,l=0,f=new lunr.Index;f.ref("id"),f.field("title",{boost:10}),f.field("body"),f.pipeline.add(lunr.trimmer,lunr.stopWordFilter),$(e),$(n)}(),function(){"use strict";function e(e,i,o,s){var a={},u=0,c=0,l=document.title,f=function(){a={},u=$(document).height(),c=$(window).height(),e.find(i).each(function(){var e=$(this).attr("href");"#"===e[0]&&(a[e]=$(e).offset().top)})},d=function(){var n=$(document).scrollTop()+s;n+c>=u&&(n=u+1e3);var r=null;for(var f in a)(a[f]a[r]||null===r)&&(r=f);n!=s||t||(r=window.location.hash,t=!0);var d=e.find("[href='"+r+"']").first();if(!d.hasClass("active")){e.find(".active").removeClass("active"),e.find(".active-parent").removeClass("active-parent"),d.addClass("active"),d.parents(o).addClass("active").siblings(i).addClass("active-parent"),d.siblings(o).addClass("active"),e.find(o).filter(":not(.active)").slideUp(150),e.find(o).filter(".active").slideDown(150),window.history.replaceState&&window.history.replaceState(null,"",r);var p=d.data("title");void 0!==p&&p.length>0?document.title=p+" – "+l:document.title=l}};!function(){f(),d(),$("#nav-button").click(function(){return $(".toc-wrapper").toggleClass("open"),$("#nav-button").toggleClass("open"),!1}),$(".page-wrapper").click(r),$(".toc-link").click(r),e.find(i).click(function(){setTimeout(function(){d()},0)}),$(window).scroll(n(d,200)),$(window).resize(n(f,200))}(),window.recacheHeights=f,window.refreshToc=d}var t=!1,n=function(e,t){var n=!1;return function(){!1===n&&(setTimeout(function(){e(),n=!1},t),n=!0)}},r=function(){$(".toc-wrapper").removeClass("open"),$("#nav-button").removeClass("open")};window.loadToc=e}(),$(function(){loadToc($("#toc"),".toc-link",".toc-list-h2, .toc-list-h3, .toc-list-h4, .toc-list-h5, .toc-list-h6",10),setupLanguages($("body").data("languages")),$(".content").imagesLoaded(function(){window.recacheHeights(),window.refreshToc()})}),window.onpopstate=function(){activateLanguage(getLanguageFromQueryString())}; - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + NAV @@ -1512,6 +1539,10 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc Go + + PHP + +
    @@ -1524,7 +1555,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
    • - Wekan REST API v3.90 + Wekan REST API v5.38
    • @@ -1544,12 +1575,12 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
      • - login + login
      • - register + register
      • @@ -1563,42 +1594,52 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
        • - get_public_boards + get_public_boards
        • - new_board + new_board
        • - get_board + get_board
        • - delete_board + delete_board
        • - export + get_board_attachments
        • - add_board_label + exportJson
        • - set_board_member_permission + add_board_label
        • - get_boards_from_user + set_board_member_permission + +
        • + +
        • + get_boards_count + +
        • + +
        • + get_boards_from_user
        • @@ -1612,22 +1653,22 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
          • - get_board_card_checklists + get_all_checklists
          • - post_board_card_checklists + new_checklist
          • - get_board_card_checklist + get_checklist
          • - delete_board_card_checklist + delete_checklist
          • @@ -1641,17 +1682,17 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
            • - get_board_card_checklist_item + get_checklist_item
            • - put_board_card_checklist_item + edit_checklist_item
            • - delete_board_card_checklist_item + delete_checklist_item
            • @@ -1665,124 +1706,22 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc - - - -
            • - CustomFields - - - -
            • - -
            • - Integrations - - - -
            • - -
            • - Lists - -
                - -
              • - get_all_lists - -
              • - -
              • - new_list - -
              • - -
              • - get_list - -
              • - -
              • - delete_list + delete_comment
              • @@ -1796,32 +1735,159 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc + + + +
              • + CustomFields + + + +
              • + +
              • + Integrations + + + +
              • + +
              • + Lists + +
                  + +
                • + get_all_lists + +
                • + +
                • + new_list + +
                • + +
                • + get_list + +
                • + +
                • + delete_list
                • @@ -1835,42 +1901,47 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
                  • - add_board_member + add_board_member
                  • - post_board_user_remove + remove_board_member
                  • - get_current_user + create_user_token
                  • - get_all_users + get_current_user
                  • - new_user + get_all_users
                  • - get_user + new_user
                  • - edit_user + get_user
                  • - delete_user + edit_user + +
                  • + +
                  • + delete_user
                  • @@ -1884,22 +1955,22 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
                    • - get_all_swimlanes + get_all_swimlanes
                    • - new_swimlane + new_swimlane
                    • - get_swimlane + get_swimlane
                    • - delete_board_swimlane + delete_swimlane
                    • @@ -1913,92 +1984,132 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
                      • - Boards + Boards
                      • - BoardsLabels + BoardsLabels
                      • - BoardsMembers + BoardsMembers
                      • - CardComments + BoardsOrgs
                      • - Cards + BoardsTeams
                      • - CardsCustomfields + CardComments
                      • - ChecklistItems + Cards
                      • - Checklists + CardsVote
                      • - CustomFields + CardsPoker
                      • - CustomFieldsSettings + CardsCustomfields
                      • - CustomFieldsSettingsDropdownitems + ChecklistItems
                      • - Integrations + Checklists
                      • - Lists + CustomFields
                      • - ListsWiplimit + CustomFieldsSettings
                      • - Swimlanes + CustomFieldsSettingsDropdownitems
                      • - Users + Integrations
                      • - UsersEmails + Lists
                      • - UsersProfile + ListsWiplimit + +
                      • + +
                      • + Swimlanes + +
                      • + +
                      • + Users + +
                      • + +
                      • + UsersProfile + +
                      • + +
                      • + UsersSessiondata + +
                      • + +
                      • + UsersOrgs + +
                      • + +
                      • + UsersTeams + +
                      • + +
                      • + UsersEmails + +
                      • + +
                      • + UsersProfileNotifications
                      • @@ -2017,7 +2128,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
                        -

                        Wekan REST API v3.90

                        +

                        Wekan REST API v5.38

                        Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.

                        @@ -2049,47 +2160,28 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
                        # You can also use wget
                         curl -X POST /users/login \
                        -  -H 'Content-Type: application/x-www-form-urlencoded' \
                        -  -H 'Accept: */*'
                        +  -H 'Content-Type: application/x-www-form-urlencoded' \
                        +  -H 'Accept: */*'
                         
                         
                        -
                        POST /users/login HTTP/1.1
                        +
                        POST /users/login HTTP/1.1
                         
                        -Content-Type: application/x-www-form-urlencoded
                        -Accept: */*
                        +Content-Type: application/x-www-form-urlencoded
                        +Accept: */*
                         
                        -
                        -
                        var headers = {
                        -  'Content-Type':'application/x-www-form-urlencoded',
                        -  'Accept':'*/*'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/users/login',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "username": "string",
                        -  "password": "pa$$word"
                        -}';
                        +
                        +
                        const inputBody = '{
                        +  "username": "string",
                        +  "password": "pa$$word"
                        +}';
                         const headers = {
                        -  'Content-Type':'application/x-www-form-urlencoded',
                        -  'Accept':'*/*'
                        -
                        +  'Content-Type':'application/x-www-form-urlencoded',
                        +  'Accept':'*/*'
                         };
                         
                        -fetch('/users/login',
                        +fetch('/users/login',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -2100,15 +2192,38 @@ fetch('/users/login',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "username": "string",
                        +  "password": "pa$$word"
                        +};
                        +const headers = {
                        +  'Content-Type':'application/x-www-form-urlencoded',
                        +  'Accept':'*/*'
                        +};
                        +
                        +fetch('/users/login',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'application/x-www-form-urlencoded',
                        -  'Accept' => '*/*'
                        +  'Content-Type' => 'application/x-www-form-urlencoded',
                        +  'Accept' => '*/*'
                         }
                         
                        -result = RestClient.post '/users/login',
                        +result = RestClient.post '/users/login',
                           params: {
                           }, headers: headers
                         
                        @@ -2117,20 +2232,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'application/x-www-form-urlencoded',
                        -  'Accept': '*/*'
                        +  'Content-Type': 'application/x-www-form-urlencoded',
                        +  'Accept': '*/*'
                         }
                         
                        -r = requests.post('/users/login', params={
                        +r = requests.post('/users/login', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/users/login");
                        +
                        URL obj = new URL("/users/login");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -2146,20 +2259,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"application/x-www-form-urlencoded"},
                        -        "Accept": []string{"*/*"},
                        -        
                        +        "Content-Type": []string{"application/x-www-form-urlencoded"},
                        +        "Accept": []string{"*/*"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/users/login", data)
                        +    req, err := http.NewRequest("POST", "/users/login", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -2167,6 +2279,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/x-www-form-urlencoded',
                        +    'Accept' => '*/*',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/users/login', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /users/login

                        Login with REST API

                        @@ -2193,7 +2330,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -2323,48 +2460,29 @@ This operation does not require authentication
                        # You can also use wget
                         curl -X POST /users/register \
                        -  -H 'Content-Type: application/x-www-form-urlencoded' \
                        -  -H 'Accept: */*'
                        +  -H 'Content-Type: application/x-www-form-urlencoded' \
                        +  -H 'Accept: */*'
                         
                         
                        -
                        POST /users/register HTTP/1.1
                        +
                        POST /users/register HTTP/1.1
                         
                        -Content-Type: application/x-www-form-urlencoded
                        -Accept: */*
                        +Content-Type: application/x-www-form-urlencoded
                        +Accept: */*
                         
                        -
                        -
                        var headers = {
                        -  'Content-Type':'application/x-www-form-urlencoded',
                        -  'Accept':'*/*'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/users/register',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "username": "string",
                        -  "password": "pa$$word",
                        -  "email": "string"
                        -}';
                        +
                        +
                        const inputBody = '{
                        +  "username": "string",
                        +  "password": "pa$$word",
                        +  "email": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'application/x-www-form-urlencoded',
                        -  'Accept':'*/*'
                        -
                        +  'Content-Type':'application/x-www-form-urlencoded',
                        +  'Accept':'*/*'
                         };
                         
                        -fetch('/users/register',
                        +fetch('/users/register',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -2375,15 +2493,39 @@ fetch('/users/register',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "username": "string",
                        +  "password": "pa$$word",
                        +  "email": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'application/x-www-form-urlencoded',
                        +  'Accept':'*/*'
                        +};
                        +
                        +fetch('/users/register',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'application/x-www-form-urlencoded',
                        -  'Accept' => '*/*'
                        +  'Content-Type' => 'application/x-www-form-urlencoded',
                        +  'Accept' => '*/*'
                         }
                         
                        -result = RestClient.post '/users/register',
                        +result = RestClient.post '/users/register',
                           params: {
                           }, headers: headers
                         
                        @@ -2392,20 +2534,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'application/x-www-form-urlencoded',
                        -  'Accept': '*/*'
                        +  'Content-Type': 'application/x-www-form-urlencoded',
                        +  'Accept': '*/*'
                         }
                         
                        -r = requests.post('/users/register', params={
                        +r = requests.post('/users/register', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/users/register");
                        +
                        URL obj = new URL("/users/register");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -2421,20 +2561,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"application/x-www-form-urlencoded"},
                        -        "Accept": []string{"*/*"},
                        -        
                        +        "Content-Type": []string{"application/x-www-form-urlencoded"},
                        +        "Accept": []string{"*/*"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/users/register", data)
                        +    req, err := http.NewRequest("POST", "/users/register", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -2442,6 +2581,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/x-www-form-urlencoded',
                        +    'Accept' => '*/*',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/users/register', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /users/register

                        Register with REST API

                        @@ -2473,7 +2637,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -2611,43 +2775,24 @@ This operation does not require authentication
                        # You can also use wget
                         curl -X GET /api/boards \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards HTTP/1.1
                        +
                        GET /api/boards HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards',
                        +fetch('/api/boards',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -2658,15 +2803,35 @@ fetch('/api/boards',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards',
                        +result = RestClient.get '/api/boards',
                           params: {
                           }, headers: headers
                         
                        @@ -2675,20 +2840,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards', params={
                        +r = requests.get('/api/boards', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards");
                        +
                        URL obj = new URL("/api/boards");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -2704,20 +2867,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards", data)
                        +    req, err := http.NewRequest("GET", "/api/boards", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -2725,6 +2887,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards

                        Get all public boards

                        @@ -2736,8 +2923,8 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "title": "string"
                        +    "_id": "string",
                        +    "title": "string"
                           }
                         ]
                         
                        @@ -2800,57 +2987,37 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards HTTP/1.1
                        +
                        POST /api/boards HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "title": "string",
                        -  "owner": "string",
                        -  "isAdmin": true,
                        -  "isActive": true,
                        -  "isNoComments": true,
                        -  "isCommentOnly": true,
                        -  "isWorker": true,
                        -  "permission": "string",
                        -  "color": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "title": "string",
                        +  "owner": "string",
                        +  "isAdmin": true,
                        +  "isActive": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true,
                        +  "isWorker": true,
                        +  "permission": "string",
                        +  "color": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards',
                        +fetch('/api/boards',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -2861,16 +3028,47 @@ fetch('/api/boards',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "title": "string",
                        +  "owner": "string",
                        +  "isAdmin": true,
                        +  "isActive": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true,
                        +  "isWorker": true,
                        +  "permission": "string",
                        +  "color": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards',
                        +result = RestClient.post '/api/boards',
                           params: {
                           }, headers: headers
                         
                        @@ -2879,21 +3077,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards', params={
                        +r = requests.post('/api/boards', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards");
                        +
                        URL obj = new URL("/api/boards");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -2909,21 +3105,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards", data)
                        +    req, err := http.NewRequest("POST", "/api/boards", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -2931,6 +3126,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards

                        Create a board

                        @@ -2969,7 +3190,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -3049,8 +3270,8 @@ want public Wekan board

                        200 Response

                        {
                        -  "_id": "string",
                        -  "defaultSwimlaneId": "string"
                        +  "_id": "string",
                        +  "defaultSwimlaneId": "string"
                         }
                         

                        Responses

                        @@ -3112,43 +3333,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board} HTTP/1.1
                        +
                        GET /api/boards/{board} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}',
                        +fetch('/api/boards/{board}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -3159,15 +3361,35 @@ fetch('/api/boards/{board}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}',
                        +result = RestClient.get '/api/boards/{board}',
                           params: {
                           }, headers: headers
                         
                        @@ -3176,20 +3398,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}', params={
                        +r = requests.get('/api/boards/{board}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}");
                        +
                        URL obj = new URL("/api/boards/{board}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -3205,20 +3425,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -3226,6 +3445,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}

                        Get the board with that particular ID

                        @@ -3250,7 +3494,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board to retrieve the data

                        Example responses

                        @@ -3259,59 +3503,77 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "title": "string",
                        -  "slug": "string",
                        -  "archived": true,
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "stars": 0,
                        -  "labels": [
                        +  "title": "string",
                        +  "slug": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "stars": 0,
                        +  "labels": [
                             {
                        -      "_id": "string",
                        -      "name": "string",
                        -      "color": "green"
                        +      "_id": "string",
                        +      "name": "string",
                        +      "color": "white"
                             }
                           ],
                        -  "members": [
                        +  "members": [
                             {
                        -      "userId": "string",
                        -      "isAdmin": true,
                        -      "isActive": true,
                        -      "isNoComments": true,
                        -      "isCommentOnly": true,
                        -      "isWorker": true
                        +      "userId": "string",
                        +      "isAdmin": true,
                        +      "isActive": true,
                        +      "isNoComments": true,
                        +      "isCommentOnly": true,
                        +      "isWorker": true
                             }
                           ],
                        -  "permission": "public",
                        -  "color": "belize",
                        -  "description": "string",
                        -  "subtasksDefaultBoardId": "string",
                        -  "subtasksDefaultListId": "string",
                        -  "dateSettingsDefaultBoardId": "string",
                        -  "dateSettingsDefaultListId": "string",
                        -  "allowsSubtasks": true,
                        -  "allowsAttachments": true,
                        -  "allowsChecklists": true,
                        -  "allowsComments": true,
                        -  "allowsDescriptionTitle": true,
                        -  "allowsDescriptionText": true,
                        -  "allowsActivities": true,
                        -  "allowsLabels": true,
                        -  "allowsAssignee": true,
                        -  "allowsMembers": true,
                        -  "allowsRequestedBy": true,
                        -  "allowsAssignedBy": true,
                        -  "allowsReceivedDate": true,
                        -  "allowsStartDate": true,
                        -  "allowsEndDate": true,
                        -  "allowsDueDate": true,
                        -  "presentParentTask": "prefix-with-full-path",
                        -  "startAt": "string",
                        -  "dueAt": "string",
                        -  "endAt": "string",
                        -  "spentTime": 0,
                        -  "isOvertime": true,
                        -  "type": "string"
                        +  "permission": "public",
                        +  "orgs": [
                        +    {
                        +      "orgId": "string",
                        +      "orgDisplayName": "string",
                        +      "isActive": true
                        +    }
                        +  ],
                        +  "teams": [
                        +    {
                        +      "teamId": "string",
                        +      "teamDisplayName": "string",
                        +      "isActive": true
                        +    }
                        +  ],
                        +  "color": "belize",
                        +  "description": "string",
                        +  "subtasksDefaultBoardId": "string",
                        +  "subtasksDefaultListId": "string",
                        +  "dateSettingsDefaultBoardId": "string",
                        +  "dateSettingsDefaultListId": "string",
                        +  "allowsSubtasks": true,
                        +  "allowsAttachments": true,
                        +  "allowsChecklists": true,
                        +  "allowsComments": true,
                        +  "allowsDescriptionTitle": true,
                        +  "allowsDescriptionText": true,
                        +  "allowsActivities": true,
                        +  "allowsLabels": true,
                        +  "allowsCreator": true,
                        +  "allowsAssignee": true,
                        +  "allowsMembers": true,
                        +  "allowsRequestedBy": true,
                        +  "allowsCardSortingByNumber": true,
                        +  "allowsAssignedBy": true,
                        +  "allowsReceivedDate": true,
                        +  "allowsStartDate": true,
                        +  "allowsEndDate": true,
                        +  "allowsDueDate": true,
                        +  "presentParentTask": "prefix-with-full-path",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": 0,
                        +  "isOvertime": true,
                        +  "type": "board",
                        +  "sort": 0
                         }
                         

                        Responses

                        @@ -3344,38 +3606,20 @@ UserSecurity
                        # You can also use wget
                         curl -X DELETE /api/boards/{board} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board} HTTP/1.1
                        -
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        +
                        DELETE /api/boards/{board} HTTP/1.1
                         
                         
                        -
                        const fetch = require('node-fetch');
                        -
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}',
                        +fetch('/api/boards/{board}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -3386,14 +3630,33 @@ fetch('/api/boards/{board}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}',
                        +result = RestClient.delete '/api/boards/{board}',
                           params: {
                           }, headers: headers
                         
                        @@ -3402,19 +3665,17 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}', params={
                        +r = requests.delete('/api/boards/{board}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}");
                        +
                        URL obj = new URL("/api/boards/{board}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -3430,19 +3691,18 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -3450,6 +3710,30 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}

                        Delete a board

                        @@ -3474,7 +3758,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board

                        Responses

                        @@ -3499,45 +3783,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        export

                        -

                        +

                        get_board_attachments

                        +

                        Code samples

                        # You can also use wget
                        -curl -X GET /api/boards/{board}/export \
                        -  -H 'Authorization: API_KEY'
                        +curl -X GET /api/boards/{board}/attachments \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/export HTTP/1.1
                        +
                        GET /api/boards/{board}/attachments HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/export',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/export',
                        +fetch('/api/boards/{board}/attachments',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -3548,14 +3818,35 @@ fetch('/api/boards/{board}/export',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/attachments',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/export',
                        +result = RestClient.get '/api/boards/{board}/attachments',
                           params: {
                           }, headers: headers
                         
                        @@ -3564,19 +3855,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/export', params={
                        +r = requests.get('/api/boards/{board}/attachments', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/export");
                        +
                        URL obj = new URL("/api/boards/{board}/attachments");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -3592,19 +3882,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/export", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/attachments", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -3612,14 +3902,297 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/attachments', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards/{board}/attachments

                        +

                        Get the list of attachments of a board

                        +

                        Parameters

                        +
                        + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "attachmentId": "string",
                        +    "attachmentName": "string",
                        +    "attachmentType": "string",
                        +    "cardId": "string",
                        +    "listId": "string",
                        +    "swimlaneId": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » attachmentIdstringfalsenonenone
                        » attachmentNamestringfalsenonenone
                        » attachmentTypestringfalsenonenone
                        » cardIdstringfalsenonenone
                        » listIdstringfalsenonenone
                        » swimlaneIdstringfalsenonenone
                        + +

                        exportJson

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/boards/{board}/export \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/boards/{board}/export HTTP/1.1
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/export',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/export',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/boards/{board}/export',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/boards/{board}/export', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/export");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/export", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/export', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/export

                        -

                        This route is used to export the board.

                        +

                        This route is used to export the board to a json file format.

                        If user is already logged-in, pass loginToken as param "authToken": '/api/boards/:boardId/export?authToken=:token'

                        See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ for detailed explanations

                        -

                        Parameters

                        +

                        Parameters

                        @@ -3640,9 +4213,9 @@ for detailed explanations

                        -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board we are exporting

                        -

                        Responses

                        +

                        Responses

                        @@ -3672,49 +4245,29 @@ UserSecurity
                        # You can also use wget
                         curl -X PUT /api/boards/{board}/labels \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        PUT /api/boards/{board}/labels HTTP/1.1
                        +
                        PUT /api/boards/{board}/labels HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/labels',
                        -  method: 'put',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "label": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "label": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/labels',
                        +fetch('/api/boards/{board}/labels',
                         {
                        -  method: 'PUT',
                        +  method: 'PUT',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -3725,16 +4278,39 @@ fetch('/api/boards/{board}/labels',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "label": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/labels',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.put '/api/boards/{board}/labels',
                        +result = RestClient.put '/api/boards/{board}/labels',
                           params: {
                           }, headers: headers
                         
                        @@ -3743,21 +4319,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.put('/api/boards/{board}/labels', params={
                        +r = requests.put('/api/boards/{board}/labels', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/labels");
                        +
                        URL obj = new URL("/api/boards/{board}/labels");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("PUT");
                        +con.setRequestMethod("PUT");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -3773,21 +4347,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("PUT", "/api/boards/{board}/labels", data)
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/labels", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -3795,6 +4368,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/labels', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        PUT /api/boards/{board}/labels

                        Add a label to a board

                        @@ -3829,7 +4428,7 @@ adds the label to the board.

                        - + @@ -3841,7 +4440,7 @@ adds the label to the board.

                        body body objectfalsetrue none
                        -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board

                        Example responses

                        @@ -3849,7 +4448,7 @@ adds the label to the board.

                        200 Response

                        -
                        "string"
                        +
                        "string"
                         

                        Responses

                        @@ -3881,48 +4480,29 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/members/{member} \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/members/{member} HTTP/1.1
                        +
                        POST /api/boards/{board}/members/{member} HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/members/{member}',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "isAdmin": true,
                        -  "isNoComments": true,
                        -  "isCommentOnly": true,
                        -  "isWorker": true
                        -}';
                        +
                        const inputBody = '{
                        +  "isAdmin": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true,
                        +  "isWorker": true
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/members/{member}',
                        +fetch('/api/boards/{board}/members/{member}',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -3933,15 +4513,40 @@ fetch('/api/boards/{board}/members/{member}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "isAdmin": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true,
                        +  "isWorker": true
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/members/{member}',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/members/{member}',
                        +result = RestClient.post '/api/boards/{board}/members/{member}',
                           params: {
                           }, headers: headers
                         
                        @@ -3950,20 +4555,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/members/{member}', params={
                        +r = requests.post('/api/boards/{board}/members/{member}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/members/{member}");
                        +
                        URL obj = new URL("/api/boards/{board}/members/{member}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -3979,20 +4582,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{member}", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{member}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4000,6 +4602,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/members/{member}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/members/{member}

                        Change the permission of a member of a board

                        @@ -4042,7 +4669,7 @@ System.out.println(response.toString());
                        - + @@ -4075,7 +4702,7 @@ System.out.println(response.toString());
                        body body objectfalsetrue none
                        -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board that we are changing

                        member: the ID of the user to change permissions

                        Responses

                        @@ -4101,50 +4728,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        get_boards_from_user

                        -

                        +

                        get_boards_count

                        +

                        Code samples

                        # You can also use wget
                        -curl -X GET /api/users/{user}/boards \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +curl -X GET /api/boards_count \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/users/{user}/boards HTTP/1.1
                        +
                        GET /api/boards_count HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users/{user}/boards',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users/{user}/boards',
                        +fetch('/api/boards_count',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -4155,15 +4763,35 @@ fetch('/api/users/{user}/boards',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards_count',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/users/{user}/boards',
                        +result = RestClient.get '/api/boards_count',
                           params: {
                           }, headers: headers
                         
                        @@ -4172,20 +4800,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/users/{user}/boards', params={
                        +r = requests.get('/api/boards_count', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users/{user}/boards");
                        +
                        URL obj = new URL("/api/boards_count");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -4201,20 +4827,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/users/{user}/boards", data)
                        +    req, err := http.NewRequest("GET", "/api/boards_count", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4222,6 +4847,241 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards_count', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards_count

                        +

                        Get public and private boards count

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "private": 0,
                        +  "public": 0
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » privateintegerfalsenonenone
                        » publicintegerfalsenonenone
                        + +

                        get_boards_from_user

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/users/{user}/boards \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/users/{user}/boards HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users/{user}/boards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users/{user}/boards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/users/{user}/boards',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/users/{user}/boards', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/users/{user}/boards");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/users/{user}/boards", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/users/{user}/boards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/users/{user}/boards

                        Get all boards attached to a user

                        @@ -4246,7 +5106,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        user: the ID of the user to retrieve the data

                        Example responses

                        @@ -4256,8 +5116,8 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "title": "string"
                        +    "_id": "string",
                        +    "title": "string"
                           }
                         ]
                         
                        @@ -4314,45 +5174,31 @@ To perform this operation, you must be authenticated by means of one of the foll UserSecurity

                        Checklists

                        -

                        get_board_card_checklists

                        -

                        +

                        get_all_checklists

                        +

                        Code samples

                        # You can also use wget
                         curl -X GET /api/boards/{board}/cards/{card}/checklists \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/cards/{card}/checklists HTTP/1.1
                        +
                        GET /api/boards/{board}/cards/{card}/checklists HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists',
                        +fetch('/api/boards/{board}/cards/{card}/checklists',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -4363,14 +5209,35 @@ fetch('/api/boards/{board}/cards/{card}/checklists'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/cards/{card}/checklists',
                        +result = RestClient.get '/api/boards/{board}/cards/{card}/checklists',
                           params: {
                           }, headers: headers
                         
                        @@ -4379,19 +5246,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/cards/{card}/checklists', params={
                        +r = requests.get('/api/boards/{board}/cards/{card}/checklists', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -4407,19 +5273,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4427,9 +5293,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cards/{card}/checklists', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/cards/{card}/checklists

                        -

                        Parameters

                        +

                        Get the list of checklists attached to a card

                        +

                        Parameters

                        @@ -4446,18 +5338,34 @@ System.out.println(response.toString()); - + - +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "_id": "string",
                        +    "title": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        @@ -4472,7 +5380,36 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        @@ -4480,53 +5417,37 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        post_board_card_checklists

                        -

                        +

                        new_checklist

                        +

                        Code samples

                        # You can also use wget
                         curl -X POST /api/boards/{board}/cards/{card}/checklists \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/cards/{card}/checklists HTTP/1.1
                        +
                        POST /api/boards/{board}/cards/{card}/checklists HTTP/1.1
                         
                         Content-Type: multipart/form-data
                        +Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "title": "string",
                        -  "items": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "title": "string",
                        +  "items": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists',
                        +fetch('/api/boards/{board}/cards/{card}/checklists',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -4537,15 +5458,40 @@ fetch('/api/boards/{board}/cards/{card}/checklists'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "title": "string",
                        +  "items": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/cards/{card}/checklists',
                        +result = RestClient.post '/api/boards/{board}/cards/{card}/checklists',
                           params: {
                           }, headers: headers
                         
                        @@ -4554,20 +5500,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/cards/{card}/checklists', params={
                        +r = requests.post('/api/boards/{board}/cards/{card}/checklists', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -4583,20 +5528,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/cards/{card}/checklists", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/cards/{card}/checklists", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4604,8 +5549,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/cards/{card}/checklists', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/cards/{card}/checklists

                        +

                        create a new checklist

                        Body parameter

                        @@ -4613,7 +5585,7 @@ System.out.println(response.toString()); items: string
                        -

                        Parameters

                        +

                        Parameters

                        @@ -4630,20 +5602,20 @@ System.out.println(response.toString()); - + - + - + @@ -4651,18 +5623,31 @@ System.out.println(response.toString()); - + - - + +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        body body objectfalsetrue none
                        body string truethe title valuethe title of the new checklist
                        » items body stringtruethe items valuefalsethe list of items on the new checklist
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -4677,7 +5662,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -4685,45 +5692,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        get_board_card_checklist

                        -

                        +

                        get_checklist

                        +

                        Code samples

                        # You can also use wget
                         curl -X GET /api/boards/{board}/cards/{card}/checklists/{checklist} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/cards/{card}/checklists/{checklist} HTTP/1.1
                        +
                        GET /api/boards/{board}/cards/{card}/checklists/{checklist} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -4734,14 +5727,35 @@ fetch('/api/boards/{board}/cards/{card}/checklists/{ch
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +result = RestClient.get '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                           params: {
                           }, headers: headers
                         
                        @@ -4750,19 +5764,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/cards/{card}/checklists/{checklist}', params={
                        +r = requests.get('/api/boards/{board}/cards/{card}/checklists/{checklist}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -4778,19 +5791,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists/{checklist}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists/{checklist}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4798,9 +5811,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cards/{card}/checklists/{checklist}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/cards/{card}/checklists/{checklist}

                        -

                        Parameters

                        +

                        Get a checklist

                        +

                        Parameters

                        @@ -4817,25 +5856,50 @@ System.out.println(response.toString()); - + - + - +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        checklist path string truethe checklist valuethe ID of the checklist
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +

                        checklist: the ID of the checklist

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "cardId": "string",
                        +  "title": "string",
                        +  "finishedAt": "string",
                        +  "createdAt": "string",
                        +  "sort": 0,
                        +  "items": [
                        +    {
                        +      "_id": "string",
                        +      "title": "string",
                        +      "isFinished": true
                        +    }
                        +  ]
                        +}
                        +
                        +

                        Responses

                        @@ -4850,7 +5914,85 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » cardIdstringfalsenonenone
                        » titlestringfalsenonenone
                        » finishedAtstringfalsenonenone
                        » createdAtstringfalsenonenone
                        » sortnumberfalsenonenone
                        » items[object]falsenonenone
                        »» _idstringfalsenonenone
                        »» titlestringfalsenonenone
                        »» isFinishedbooleanfalsenonenone
                        @@ -4858,45 +6000,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_card_checklist

                        -

                        +

                        delete_checklist

                        +

                        Code samples

                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/cards/{card}/checklists/{checklist} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -4907,14 +6035,35 @@ fetch('/api/boards/{board}/cards/{card}/checklists/{ch
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                        +result = RestClient.delete '/api/boards/{board}/cards/{card}/checklists/{checklist}',
                           params: {
                           }, headers: headers
                         
                        @@ -4923,19 +6072,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/cards/{card}/checklists/{checklist}', params={
                        +r = requests.delete('/api/boards/{board}/cards/{card}/checklists/{checklist}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -4951,19 +6099,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/checklists/{checklist}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/checklists/{checklist}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -4971,9 +6119,36 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/cards/{card}/checklists/{checklist}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist}

                        -

                        Parameters

                        +

                        Delete a checklist

                        +

                        The checklist will be removed, not put in the recycle bin.

                        +

                        Parameters

                        @@ -4990,25 +6165,39 @@ System.out.println(response.toString()); - + - + - +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        checklist path string truethe checklist valuethe ID of the checklist to remove
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +

                        checklist: the ID of the checklist to remove

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -5023,7 +6212,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -5032,45 +6243,31 @@ To perform this operation, you must be authenticated by means of one of the foll UserSecurity

                        ChecklistItems

                        -

                        get_board_card_checklist_item

                        -

                        +

                        get_checklist_item

                        +

                        Code samples

                        # You can also use wget
                         curl -X GET /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                        +
                        GET /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -5081,14 +6278,35 @@ fetch('/api/boards/{board}/cards/{card}/checklists/{ch
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +result = RestClient.get '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                           params: {
                           }, headers: headers
                         
                        @@ -5097,19 +6315,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', params={
                        +r = requests.get('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -5125,19 +6342,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -5145,9 +6362,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}

                        -

                        Parameters

                        +

                        Get a checklist item

                        +

                        Parameters

                        @@ -5164,32 +6407,53 @@ System.out.println(response.toString()); - + - + - + - +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        checklist path string truethe checklist valuethe checklist ID
                        item path string truethe item valuethe ID of the item
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +

                        checklist: the checklist ID

                        +

                        item: the ID of the item

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "title": "string",
                        +  "sort": 0,
                        +  "isFinished": true,
                        +  "checklistId": "string",
                        +  "cardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -5204,7 +6468,7 @@ System.out.println(response.toString()); - +
                        200 OK 200 responseNoneChecklistItems
                        @@ -5212,53 +6476,37 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        put_board_card_checklist_item

                        -

                        +

                        edit_checklist_item

                        +

                        Code samples

                        # You can also use wget
                         curl -X PUT /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        PUT /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                        +
                        PUT /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                         
                         Content-Type: multipart/form-data
                        +Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        -  method: 'put',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "isFinished": "string",
                        -  "title": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "isFinished": "string",
                        +  "title": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                         {
                        -  method: 'PUT',
                        +  method: 'PUT',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -5269,15 +6517,40 @@ fetch('/api/boards/{board}/cards/{card}/checklists/{ch
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "isFinished": "string",
                        +  "title": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.put '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +result = RestClient.put '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                           params: {
                           }, headers: headers
                         
                        @@ -5286,20 +6559,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.put('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', params={
                        +r = requests.put('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("PUT");
                        +con.setRequestMethod("PUT");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -5315,20 +6587,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("PUT", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -5336,8 +6608,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        PUT /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}

                        +

                        Edit a checklist item

                        Body parameter

                        @@ -5345,7 +6644,7 @@ System.out.println(response.toString()); title: string
                        -

                        Parameters

                        +

                        Parameters

                        @@ -5362,28 +6661,28 @@ System.out.println(response.toString()); - + - + - + - + @@ -5396,19 +6695,34 @@ System.out.println(response.toString()); - - + + - - + +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        checklist path string truethe checklist valuethe checklist ID
                        item path string truethe item valuethe ID of the item
                        body» isFinished body stringtruethe isFinished valuefalseis the item checked?
                        » title body stringtruethe title valuefalsethe new text of the item
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +

                        checklist: the checklist ID

                        +

                        item: the ID of the item

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -5423,7 +6737,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -5431,45 +6767,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_card_checklist_item

                        -

                        +

                        delete_checklist_item

                        +

                        Code samples

                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -5480,14 +6802,35 @@ fetch('/api/boards/{board}/cards/{card}/checklists/{ch
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                        +result = RestClient.delete '/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}',
                           params: {
                           }, headers: headers
                         
                        @@ -5496,19 +6839,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', params={
                        +r = requests.delete('/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -5524,19 +6866,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -5544,9 +6886,36 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}

                        -

                        Parameters

                        +

                        Delete a checklist item

                        +

                        Note: this operation can't be reverted.

                        +

                        Parameters

                        @@ -5563,32 +6932,47 @@ System.out.println(response.toString()); - + - + - + - +
                        path string truethe board valuethe board ID
                        card path string truethe card valuethe card ID
                        checklist path string truethe checklist valuethe checklist ID
                        item path string truethe item valuethe ID of the item to be removed
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        card: the card ID

                        +

                        checklist: the checklist ID

                        +

                        item: the ID of the item to be removed

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -5603,7 +6987,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -5619,43 +7025,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/cards/{card}/comments \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/cards/{card}/comments HTTP/1.1
                        +
                        GET /api/boards/{board}/cards/{card}/comments HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/comments',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/comments',
                        +fetch('/api/boards/{board}/cards/{card}/comments',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -5666,15 +7053,35 @@ fetch('/api/boards/{board}/cards/{card}/comments'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/comments',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/cards/{card}/comments',
                        +result = RestClient.get '/api/boards/{board}/cards/{card}/comments',
                           params: {
                           }, headers: headers
                         
                        @@ -5683,20 +7090,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/cards/{card}/comments', params={
                        +r = requests.get('/api/boards/{board}/cards/{card}/comments', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -5712,20 +7117,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/comments", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/comments", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -5733,6 +7137,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cards/{card}/comments', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/cards/{card}/comments

                        Get all comments attached to a card

                        @@ -5764,7 +7193,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID of the card

                        card: the ID of the card

                        @@ -5775,9 +7204,9 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "comment": "string",
                        -    "authorId": "string"
                        +    "_id": "string",
                        +    "comment": "string",
                        +    "authorId": "string"
                           }
                         ]
                         
                        @@ -5840,53 +7269,37 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        post_board_card_comments

                        -

                        +

                        new_comment

                        +

                        Code samples

                        # You can also use wget
                         curl -X POST /api/boards/{board}/cards/{card}/comments \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/cards/{card}/comments HTTP/1.1
                        +
                        POST /api/boards/{board}/cards/{card}/comments HTTP/1.1
                         
                         Content-Type: multipart/form-data
                        +Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/comments',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "authorId": "string",
                        -  "comment": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "authorId": "string",
                        +  "comment": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/comments',
                        +fetch('/api/boards/{board}/cards/{card}/comments',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -5897,15 +7310,40 @@ fetch('/api/boards/{board}/cards/{card}/comments'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "authorId": "string",
                        +  "comment": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/comments',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/cards/{card}/comments',
                        +result = RestClient.post '/api/boards/{board}/cards/{card}/comments',
                           params: {
                           }, headers: headers
                         
                        @@ -5914,20 +7352,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/cards/{card}/comments', params={
                        +r = requests.post('/api/boards/{board}/cards/{card}/comments', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -5943,20 +7380,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/cards/{card}/comments", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/cards/{card}/comments", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -5964,8 +7401,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/cards/{card}/comments', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/cards/{card}/comments

                        +

                        Add a comment on a card

                        Body parameter

                        @@ -5973,7 +7437,7 @@ System.out.println(response.toString()); comment: string
                        -

                        Parameters

                        +

                        Parameters

                        @@ -5990,20 +7454,20 @@ System.out.println(response.toString()); - + - + - + @@ -6011,7 +7475,7 @@ System.out.println(response.toString()); - + @@ -6022,7 +7486,20 @@ System.out.println(response.toString());
                        path string truethe board valuethe board ID of the card
                        card path string truethe card valuethe ID of the card
                        body body objectfalsetrue none
                        body string truethe authorId valuethe user who 'posted' the comment
                        » comment
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID of the card

                        +

                        card: the ID of the card

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -6037,7 +7514,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -6045,45 +7544,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        get_board_card_comment

                        -

                        +

                        get_comment

                        +

                        Code samples

                        # You can also use wget
                         curl -X GET /api/boards/{board}/cards/{card}/comments/{comment} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/cards/{card}/comments/{comment} HTTP/1.1
                        +
                        GET /api/boards/{board}/cards/{card}/comments/{comment} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/comments/{comment}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                        +fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -6094,14 +7579,35 @@ fetch('/api/boards/{board}/cards/{card}/comments/{comm
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/cards/{card}/comments/{comment}',
                        +result = RestClient.get '/api/boards/{board}/cards/{card}/comments/{comment}',
                           params: {
                           }, headers: headers
                         
                        @@ -6110,19 +7616,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/cards/{card}/comments/{comment}', params={
                        +r = requests.get('/api/boards/{board}/cards/{card}/comments/{comment}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments/{comment}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments/{comment}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -6138,19 +7643,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/comments/{comment}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cards/{card}/comments/{comment}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -6158,9 +7663,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cards/{card}/comments/{comment}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/cards/{card}/comments/{comment}

                        -

                        Parameters

                        +

                        Get a comment on a card

                        +

                        Parameters

                        @@ -6177,25 +7708,44 @@ System.out.println(response.toString()); - + - + - +
                        path string truethe board valuethe board ID of the card
                        card path string truethe card valuethe ID of the card
                        comment path string truethe comment valuethe ID of the comment to retrieve
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID of the card

                        +

                        card: the ID of the card

                        +

                        comment: the ID of the comment to retrieve

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "boardId": "string",
                        +  "cardId": "string",
                        +  "text": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -6210,7 +7760,7 @@ System.out.println(response.toString()); - +
                        200 OK 200 responseNoneCardComments
                        @@ -6218,45 +7768,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_card_comment

                        -

                        +

                        delete_comment

                        +

                        Code samples

                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/cards/{card}/comments/{comment} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/cards/{card}/comments/{comment} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/cards/{card}/comments/{comment} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/cards/{card}/comments/{comment}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                        +fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -6267,14 +7803,35 @@ fetch('/api/boards/{board}/cards/{card}/comments/{comm
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cards/{card}/comments/{comment}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/cards/{card}/comments/{comment}',
                        +result = RestClient.delete '/api/boards/{board}/cards/{card}/comments/{comment}',
                           params: {
                           }, headers: headers
                         
                        @@ -6283,19 +7840,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/cards/{card}/comments/{comment}', params={
                        +r = requests.delete('/api/boards/{board}/cards/{card}/comments/{comment}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments/{comment}");
                        +
                        URL obj = new URL("/api/boards/{board}/cards/{card}/comments/{comment}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -6311,19 +7867,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/comments/{comment}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/cards/{card}/comments/{comment}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -6331,9 +7887,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/cards/{card}/comments/{comment}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/cards/{card}/comments/{comment}

                        -

                        Parameters

                        +

                        Delete a comment on a card

                        +

                        Parameters

                        @@ -6350,25 +7932,39 @@ System.out.println(response.toString()); - + - + - +
                        path string truethe board valuethe board ID of the card
                        card path string truethe card valuethe ID of the card
                        comment path string truethe comment valuethe ID of the comment to delete
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID of the card

                        +

                        card: the ID of the card

                        +

                        comment: the ID of the comment to delete

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -6383,7 +7979,2177 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        Cards

                        +

                        get_cards_by_custom_field

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue} HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}

                        +

                        Get all Cards that matchs a value of a specific custom field

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        customFieldpathstringtruethe list ID
                        customFieldValuepathstringtruethe value to look for
                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        customField: the list ID

                        +

                        customFieldValue: the value to look for

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "_id": "string",
                        +    "title": "string",
                        +    "description": "string",
                        +    "listId": "string",
                        +    "swinlaneId": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        » descriptionstringfalsenonenone
                        » listIdstringfalsenonenone
                        » swinlaneIdstringfalsenonenone
                        + +

                        get_all_cards

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/boards/{board}/lists/{list}/cards \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/boards/{board}/lists/{list}/cards HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/boards/{board}/lists/{list}/cards',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/boards/{board}/lists/{list}/cards', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}/cards", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/lists/{list}/cards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards/{board}/lists/{list}/cards

                        +

                        Get all Cards attached to a List

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        listpathstringtruethe list ID
                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        list: the list ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "_id": "string",
                        +    "title": "string",
                        +    "description": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        » descriptionstringfalsenonenone
                        + +

                        new_card

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X POST /api/boards/{board}/lists/{list}/cards \
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        POST /api/boards/{board}/lists/{list}/cards HTTP/1.1
                        +
                        +Content-Type: multipart/form-data
                        +Accept: application/json
                        +
                        +
                        +
                        const inputBody = '{
                        +  "authorId": "string",
                        +  "members": "string",
                        +  "assignees": "string",
                        +  "title": "string",
                        +  "description": "string",
                        +  "swimlaneId": "string"
                        +}';
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards',
                        +{
                        +  method: 'POST',
                        +  body: inputBody,
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "authorId": "string",
                        +  "members": "string",
                        +  "assignees": "string",
                        +  "title": "string",
                        +  "description": "string",
                        +  "swimlaneId": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.post '/api/boards/{board}/lists/{list}/cards',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.post('/api/boards/{board}/lists/{list}/cards', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("POST");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/lists/{list}/cards", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/lists/{list}/cards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        POST /api/boards/{board}/lists/{list}/cards

                        +

                        Create a new Card

                        +
                        +

                        Body parameter

                        +
                        +
                        authorId: string
                        +members: string
                        +assignees: string
                        +title: string
                        +description: string
                        +swimlaneId: string
                        +
                        +
                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID of the new card
                        listpathstringtruethe list ID of the new card
                        bodybodyobjecttruenone
                        » authorIdbodystringtruethe authorId value
                        » membersbodystringfalsethe member IDs list of the new card
                        » assigneesbodystringfalsethe array of maximum one ID of assignee of the new card
                        » titlebodystringtruethe title of the new card
                        » descriptionbodystringtruethe description of the new card
                        » swimlaneIdbodystringtruethe swimlane ID of the new card
                        +

                        Detailed descriptions

                        +

                        board: the board ID of the new card

                        +

                        list: the list ID of the new card

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        get_card

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/boards/{board}/lists/{list}/cards/{card} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/boards/{board}/lists/{list}/cards/{card}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/boards/{board}/lists/{list}/cards/{card}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/lists/{list}/cards/{card}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards/{board}/lists/{list}/cards/{card}

                        +

                        Get a Card

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        listpathstringtruethe list ID of the card
                        cardpathstringtruethe card ID
                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        list: the list ID of the card

                        +

                        card: the card ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "title": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "parentId": "string",
                        +  "listId": "string",
                        +  "swimlaneId": "string",
                        +  "boardId": "string",
                        +  "coverId": "string",
                        +  "color": "white",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "customFields": [
                        +    {}
                        +  ],
                        +  "dateLastActivity": "string",
                        +  "description": "string",
                        +  "requestedBy": "string",
                        +  "assignedBy": "string",
                        +  "labelIds": [
                        +    "string"
                        +  ],
                        +  "members": [
                        +    "string"
                        +  ],
                        +  "assignees": [
                        +    "string"
                        +  ],
                        +  "receivedAt": "string",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": 0,
                        +  "isOvertime": true,
                        +  "userId": "string",
                        +  "sort": 0,
                        +  "subtaskSort": 0,
                        +  "type": "string",
                        +  "linkedId": "string",
                        +  "vote": {
                        +    "question": "string",
                        +    "positive": [
                        +      "string"
                        +    ],
                        +    "negative": [
                        +      "string"
                        +    ],
                        +    "end": "string",
                        +    "public": true,
                        +    "allowNonBoardMembers": true
                        +  },
                        +  "poker": {
                        +    "question": true,
                        +    "one": [
                        +      "string"
                        +    ],
                        +    "two": [
                        +      "string"
                        +    ],
                        +    "three": [
                        +      "string"
                        +    ],
                        +    "five": [
                        +      "string"
                        +    ],
                        +    "eight": [
                        +      "string"
                        +    ],
                        +    "thirteen": [
                        +      "string"
                        +    ],
                        +    "twenty": [
                        +      "string"
                        +    ],
                        +    "forty": [
                        +      "string"
                        +    ],
                        +    "oneHundred": [
                        +      "string"
                        +    ],
                        +    "unsure": [
                        +      "string"
                        +    ],
                        +    "end": "string",
                        +    "allowNonBoardMembers": true,
                        +    "estimation": 0
                        +  },
                        +  "targetId_gantt": [
                        +    "string"
                        +  ],
                        +  "linkType_gantt": [
                        +    0
                        +  ],
                        +  "linkId_gantt": [
                        +    "string"
                        +  ]
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseCards
                        + +

                        edit_card

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X PUT /api/boards/{board}/lists/{list}/cards/{card} \
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        PUT /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        +
                        +Content-Type: multipart/form-data
                        +Accept: application/json
                        +
                        +
                        +
                        const inputBody = '{
                        +  "title": "string",
                        +  "sort": "string",
                        +  "parentId": "string",
                        +  "description": "string",
                        +  "color": "string",
                        +  "vote": {},
                        +  "poker": {},
                        +  "labelIds": "string",
                        +  "requestedBy": "string",
                        +  "assignedBy": "string",
                        +  "receivedAt": "string",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": "string",
                        +  "isOverTime": true,
                        +  "customFields": "string",
                        +  "members": "string",
                        +  "assignees": "string",
                        +  "swimlaneId": "string",
                        +  "listId": "string",
                        +  "authorId": "string"
                        +}';
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'PUT',
                        +  body: inputBody,
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "title": "string",
                        +  "sort": "string",
                        +  "parentId": "string",
                        +  "description": "string",
                        +  "color": "string",
                        +  "vote": {},
                        +  "poker": {},
                        +  "labelIds": "string",
                        +  "requestedBy": "string",
                        +  "assignedBy": "string",
                        +  "receivedAt": "string",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": "string",
                        +  "isOverTime": true,
                        +  "customFields": "string",
                        +  "members": "string",
                        +  "assignees": "string",
                        +  "swimlaneId": "string",
                        +  "listId": "string",
                        +  "authorId": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.put '/api/boards/{board}/lists/{list}/cards/{card}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.put('/api/boards/{board}/lists/{list}/cards/{card}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("PUT");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/lists/{list}/cards/{card}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        PUT /api/boards/{board}/lists/{list}/cards/{card}

                        +

                        Edit Fields in a Card

                        +

                        Edit a card

                        +

                        The color has to be chosen between 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:

                        + Wekan card colors +

                        Note: setting the color to white has the same effect than removing it.

                        +
                        +

                        Body parameter

                        +
                        +
                        title: string
                        +sort: string
                        +parentId: string
                        +description: string
                        +color: string
                        +vote: {}
                        +poker: {}
                        +labelIds: string
                        +requestedBy: string
                        +assignedBy: string
                        +receivedAt: string
                        +startAt: string
                        +dueAt: string
                        +endAt: string
                        +spentTime: string
                        +isOverTime: true
                        +customFields: string
                        +members: string
                        +assignees: string
                        +swimlaneId: string
                        +listId: string
                        +authorId: string
                        +
                        +
                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID of the card
                        listpathstringtruethe list ID of the card
                        cardpathstringtruethe ID of the card
                        bodybodyobjectfalsenone
                        » titlebodystringfalsethe new title of the card
                        » sortbodystringfalsethe new sort value of the card
                        » parentIdbodystringfalsechange the parent of the card
                        » descriptionbodystringfalsethe new description of the card
                        » colorbodystringfalsethe new color of the card
                        » votebodyobjectfalsethe vote object
                        » pokerbodyobjectfalsethe poker object
                        » labelIdsbodystringfalsethe new list of label IDs attached to the card
                        » requestedBybodystringfalsethe new requestedBy field of the card
                        » assignedBybodystringfalsethe new assignedBy field of the card
                        » receivedAtbodystringfalsethe new receivedAt field of the card
                        » startAtbodystringfalsethe new startAt field of the card
                        » dueAtbodystringfalsethe new dueAt field of the card
                        » endAtbodystringfalsethe new endAt field of the card
                        » spentTimebodystringfalsethe new spentTime field of the card
                        » isOverTimebodybooleanfalsethe new isOverTime field of the card
                        » customFieldsbodystringfalsethe new customFields value of the card
                        » membersbodystringfalsethe new list of member IDs attached to the card
                        » assigneesbodystringfalsethe array of maximum one ID of assignee attached to the card
                        » swimlaneIdbodystringfalsethe new swimlane ID of the card
                        » listIdbodystringfalsethe new list ID of the card (move operation)
                        » authorIdbodystringfalsechange the owner of the card
                        +

                        Detailed descriptions

                        +

                        board: the board ID of the card

                        +

                        list: the list ID of the card

                        +

                        card: the ID of the card

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        delete_card

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X DELETE /api/boards/{board}/lists/{list}/cards/{card} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        DELETE /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.delete '/api/boards/{board}/lists/{list}/cards/{card}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.delete('/api/boards/{board}/lists/{list}/cards/{card}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("DELETE");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/lists/{list}/cards/{card}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        DELETE /api/boards/{board}/lists/{list}/cards/{card}

                        +

                        Delete a card from a board

                        +

                        This operation deletes a card, and therefore the card +is not put in the recycle bin.

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID of the card
                        listpathstringtruethe list ID of the card
                        cardpathstringtruethe ID of the card
                        +

                        Detailed descriptions

                        +

                        board: the board ID of the card

                        +

                        list: the list ID of the card

                        +

                        card: the ID of the card

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        get_swimlane_cards

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/boards/{board}/swimlanes/{swimlane}/cards \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/boards/{board}/swimlanes/{swimlane}/cards HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}/cards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}/cards',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/boards/{board}/swimlanes/{swimlane}/cards',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/boards/{board}/swimlanes/{swimlane}/cards', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}/cards");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes/{swimlane}/cards", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/swimlanes/{swimlane}/cards', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        GET /api/boards/{board}/swimlanes/{swimlane}/cards

                        +

                        get all cards attached to a swimlane

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        swimlanepathstringtruethe swimlane ID
                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        swimlane: the swimlane ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "_id": "string",
                        +    "title": "string",
                        +    "description": "string",
                        +    "listId": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        » descriptionstringfalsenonenone
                        » listIdstringfalsenonenone
                        @@ -6399,43 +10165,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/custom-fields \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/custom-fields HTTP/1.1
                        +
                        GET /api/boards/{board}/custom-fields HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/custom-fields',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/custom-fields',
                        +fetch('/api/boards/{board}/custom-fields',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -6446,15 +10193,35 @@ fetch('/api/boards/{board}/custom-fields',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/custom-fields',
                        +result = RestClient.get '/api/boards/{board}/custom-fields',
                           params: {
                           }, headers: headers
                         
                        @@ -6463,20 +10230,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/custom-fields', params={
                        +r = requests.get('/api/boards/{board}/custom-fields', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/custom-fields");
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -6492,20 +10257,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/custom-fields", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/custom-fields", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -6513,6 +10277,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/custom-fields', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/custom-fields

                        Get the list of Custom Fields attached to a board

                        @@ -6545,9 +10334,9 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "name": "string",
                        -    "type": "string"
                        +    "_id": "string",
                        +    "name": "string",
                        +    "type": "string"
                           }
                         ]
                         
                        @@ -6617,55 +10406,35 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/custom-fields \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/custom-fields HTTP/1.1
                        +
                        POST /api/boards/{board}/custom-fields HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/custom-fields',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "name": "string",
                        -  "type": "string",
                        -  "settings": "string",
                        -  "showOnCard": true,
                        -  "automaticallyOnCard": true,
                        -  "showLabelOnMiniCard": true,
                        -  "authorId": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "name": "string",
                        +  "type": "string",
                        +  "settings": "string",
                        +  "showOnCard": true,
                        +  "automaticallyOnCard": true,
                        +  "showLabelOnMiniCard": true,
                        +  "authorId": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/custom-fields',
                        +fetch('/api/boards/{board}/custom-fields',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -6676,16 +10445,45 @@ fetch('/api/boards/{board}/custom-fields',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "name": "string",
                        +  "type": "string",
                        +  "settings": "string",
                        +  "showOnCard": true,
                        +  "automaticallyOnCard": true,
                        +  "showLabelOnMiniCard": true,
                        +  "authorId": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/custom-fields',
                        +result = RestClient.post '/api/boards/{board}/custom-fields',
                           params: {
                           }, headers: headers
                         
                        @@ -6694,21 +10492,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/custom-fields', params={
                        +r = requests.post('/api/boards/{board}/custom-fields', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/custom-fields");
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -6724,21 +10520,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/custom-fields", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/custom-fields", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -6746,6 +10541,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/custom-fields', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/custom-fields

                        Create a Custom Field

                        @@ -6784,7 +10605,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -6845,7 +10666,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -6893,45 +10714,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        get_board_customField

                        -

                        +

                        get_custom_field

                        +

                        Code samples

                        # You can also use wget
                         curl -X GET /api/boards/{board}/custom-fields/{customField} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/custom-fields/{customField} HTTP/1.1
                        +
                        GET /api/boards/{board}/custom-fields/{customField} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/custom-fields/{customField}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/custom-fields/{customField}',
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -6942,14 +10749,35 @@ fetch('/api/boards/{board}/custom-fields/{customField}
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/custom-fields/{customField}',
                        +result = RestClient.get '/api/boards/{board}/custom-fields/{customField}',
                           params: {
                           }, headers: headers
                         
                        @@ -6958,19 +10786,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/custom-fields/{customField}', params={
                        +r = requests.get('/api/boards/{board}/custom-fields/{customField}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}");
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -6986,19 +10813,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/custom-fields/{customField}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/custom-fields/{customField}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -7006,9 +10833,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/custom-fields/{customField}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/custom-fields/{customField}

                        -

                        Parameters

                        +

                        Get a Custom Fields attached to a board

                        +

                        Parameters

                        @@ -7032,11 +10885,26 @@ System.out.println(response.toString()); - +
                        path string truethe customField valuethe ID of the custom field
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        customField: the ID of the custom field

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        [
                        +  {
                        +    "_id": "string",
                        +    "boardIds": "string"
                        +  }
                        +]
                        +
                        +

                        Responses

                        @@ -7051,7 +10919,36 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » boardIdsstringfalsenonenone
                        @@ -7059,46 +10956,43 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_customField

                        -

                        +

                        edit_custom_field

                        +

                        Code samples

                        # You can also use wget
                        -curl -X DELETE /api/boards/{board}/custom-fields/{customField} \
                        -  -H 'Authorization: API_KEY'
                        +curl -X PUT /api/boards/{board}/custom-fields/{customField} \
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/custom-fields/{customField} HTTP/1.1
                        +
                        PUT /api/boards/{board}/custom-fields/{customField} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/custom-fields/{customField}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Content-Type: multipart/form-data
                        +Accept: application/json
                         
                        +
                        +
                        const inputBody = '{
                        +  "name": "string",
                        +  "type": "string",
                        +  "settings": "string",
                        +  "showOnCard": true,
                        +  "automaticallyOnCard": true,
                        +  "alwaysOnCard": "string",
                        +  "showLabelOnMiniCard": true
                        +}';
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/custom-fields/{customField}',
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                         {
                        -  method: 'DELETE',
                        -
                        +  method: 'PUT',
                        +  body: inputBody,
                           headers: headers
                         })
                         .then(function(res) {
                        @@ -7108,14 +11002,45 @@ fetch('/api/boards/{board}/custom-fields/{customField}
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "name": "string",
                        +  "type": "string",
                        +  "settings": "string",
                        +  "showOnCard": true,
                        +  "automaticallyOnCard": true,
                        +  "alwaysOnCard": "string",
                        +  "showLabelOnMiniCard": true
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/custom-fields/{customField}',
                        +result = RestClient.put '/api/boards/{board}/custom-fields/{customField}',
                           params: {
                           }, headers: headers
                         
                        @@ -7124,19 +11049,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/custom-fields/{customField}', params={
                        +r = requests.put('/api/boards/{board}/custom-fields/{customField}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}");
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("PUT");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -7152,19 +11077,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/custom-fields/{customField}", data)
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/custom-fields/{customField}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -7173,8 +11099,47 @@ System.out.println(response.toString());
                         }
                         
                         
                        -

                        DELETE /api/boards/{board}/custom-fields/{customField}

                        -

                        Parameters

                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/custom-fields/{customField}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        PUT /api/boards/{board}/custom-fields/{customField}

                        +

                        Update a Custom Field

                        +
                        +

                        Body parameter

                        +
                        +
                        name: string
                        +type: string
                        +settings: string
                        +showOnCard: true
                        +automaticallyOnCard: true
                        +alwaysOnCard: string
                        +showLabelOnMiniCard: true
                        +
                        +
                        +

                        Parameters

                        @@ -7200,9 +11165,75 @@ System.out.println(response.toString()); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        true the customField value
                        bodybodyobjecttruenone
                        » namebodystringtruethe name of the custom field
                        » typebodystringtruethe type of the custom field
                        » settingsbodystringtruethe settings object of the custom field
                        » showOnCardbodybooleantrueshould we show the custom field on cards
                        » automaticallyOnCardbodybooleantrueshould the custom fields automatically be added on cards
                        » alwaysOnCardbodystringtruethe alwaysOnCard value
                        » showLabelOnMiniCardbodybooleantrueshould the label of the custom field be shown on minicards
                        -

                        Responses

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -7217,7 +11248,1030 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        delete_custom_field

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X DELETE /api/boards/{board}/custom-fields/{customField} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        DELETE /api/boards/{board}/custom-fields/{customField} HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.delete '/api/boards/{board}/custom-fields/{customField}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.delete('/api/boards/{board}/custom-fields/{customField}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("DELETE");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/custom-fields/{customField}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/custom-fields/{customField}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        DELETE /api/boards/{board}/custom-fields/{customField}

                        +

                        Delete a Custom Fields attached to a board

                        +

                        The Custom Field can't be retrieved after this operation

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        customFieldpathstringtruethe ID of the custom field
                        +

                        Detailed descriptions

                        +

                        customField: the ID of the custom field

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        add_custom_field_dropdown_items

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X POST /api/boards/{board}/custom-fields/{customField}/dropdown-items \
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        POST /api/boards/{board}/custom-fields/{customField}/dropdown-items HTTP/1.1
                        +
                        +Content-Type: multipart/form-data
                        +Accept: application/json
                        +
                        +
                        +
                        const inputBody = '{
                        +  "items": "string"
                        +}';
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items',
                        +{
                        +  method: 'POST',
                        +  body: inputBody,
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "items": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.post '/api/boards/{board}/custom-fields/{customField}/dropdown-items',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.post('/api/boards/{board}/custom-fields/{customField}/dropdown-items', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}/dropdown-items");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("POST");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/custom-fields/{customField}/dropdown-items", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/custom-fields/{customField}/dropdown-items', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        POST /api/boards/{board}/custom-fields/{customField}/dropdown-items

                        +

                        Update a Custom Field's dropdown items

                        +
                        +

                        Body parameter

                        +
                        +
                        items: string
                        +
                        +
                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        customFieldpathstringtruethe customField value
                        bodybodyobjectfalsenone
                        » itemsbodystringfalsenames of the custom field
                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        edit_custom_field_dropdown_item

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X PUT /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem} \
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        PUT /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem} HTTP/1.1
                        +
                        +Content-Type: multipart/form-data
                        +Accept: application/json
                        +
                        +
                        +
                        const inputBody = '{
                        +  "name": "string"
                        +}';
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +{
                        +  method: 'PUT',
                        +  body: inputBody,
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "name": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.put '/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.put('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("PUT");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        PUT /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}

                        +

                        Update a Custom Field's dropdown item

                        +
                        +

                        Body parameter

                        +
                        +
                        name: string
                        +
                        +
                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        customFieldpathstringtruethe customField value
                        dropdownItempathstringtruethe dropdownItem value
                        bodybodyobjecttruenone
                        » namebodystringtruenames of the custom field
                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        delete_custom_field_dropdown_item

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X DELETE /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        DELETE /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem} HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.delete '/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.delete('/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("DELETE");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        DELETE /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}

                        +

                        Update a Custom Field's dropdown items

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        customFieldpathstringtruethe customField value
                        dropdownItempathstringtruethe dropdownItem value
                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -7233,43 +12287,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/integrations \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/integrations HTTP/1.1
                        +
                        GET /api/boards/{board}/integrations HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations',
                        +fetch('/api/boards/{board}/integrations',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -7280,15 +12315,35 @@ fetch('/api/boards/{board}/integrations',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/integrations',
                        +result = RestClient.get '/api/boards/{board}/integrations',
                           params: {
                           }, headers: headers
                         
                        @@ -7297,20 +12352,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/integrations', params={
                        +r = requests.get('/api/boards/{board}/integrations', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -7326,20 +12379,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/integrations", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/integrations", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -7347,6 +12399,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/integrations', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/integrations

                        Get all integrations in board

                        @@ -7371,7 +12448,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        Example responses

                        @@ -7381,18 +12458,18 @@ System.out.println(response.toString());
                        [
                           {
                        -    "enabled": true,
                        -    "title": "string",
                        -    "type": "string",
                        -    "activities": [
                        -      "string"
                        +    "enabled": true,
                        +    "title": "string",
                        +    "type": "string",
                        +    "activities": [
                        +      "string"
                             ],
                        -    "url": "string",
                        -    "token": "string",
                        -    "boardId": "string",
                        -    "createdAt": "string",
                        -    "modifiedAt": "string",
                        -    "userId": "string"
                        +    "url": "string",
                        +    "token": "string",
                        +    "boardId": "string",
                        +    "createdAt": "string",
                        +    "modifiedAt": "string",
                        +    "userId": "string"
                           }
                         ]
                         
                        @@ -7444,7 +12521,7 @@ System.out.println(response.toString()); » title -string|null +string¦null false none name of the integration @@ -7472,7 +12549,7 @@ System.out.println(response.toString()); » token -string|null +string¦null false none token of the integration @@ -7518,49 +12595,29 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/integrations \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/integrations HTTP/1.1
                        +
                        POST /api/boards/{board}/integrations HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "url": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "url": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations',
                        +fetch('/api/boards/{board}/integrations',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -7571,16 +12628,39 @@ fetch('/api/boards/{board}/integrations',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "url": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/integrations',
                        +result = RestClient.post '/api/boards/{board}/integrations',
                           params: {
                           }, headers: headers
                         
                        @@ -7589,21 +12669,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/integrations', params={
                        +r = requests.post('/api/boards/{board}/integrations', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -7619,21 +12697,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/integrations", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/integrations", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -7641,6 +12718,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/integrations', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/integrations

                        Create a new integration

                        @@ -7673,7 +12776,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -7685,7 +12788,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        Example responses

                        @@ -7694,7 +12797,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -7749,43 +12852,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/integrations/{int} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/integrations/{int} HTTP/1.1
                        +
                        GET /api/boards/{board}/integrations/{int} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations/{int}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations/{int}',
                        +fetch('/api/boards/{board}/integrations/{int}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -7796,15 +12880,35 @@ fetch('/api/boards/{board}/integrations/{int}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations/{int}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/integrations/{int}',
                        +result = RestClient.get '/api/boards/{board}/integrations/{int}',
                           params: {
                           }, headers: headers
                         
                        @@ -7813,20 +12917,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/integrations/{int}', params={
                        +r = requests.get('/api/boards/{board}/integrations/{int}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -7842,20 +12944,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/integrations/{int}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/integrations/{int}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -7863,6 +12964,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/integrations/{int}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/integrations/{int}

                        Get a single integration in board

                        @@ -7894,7 +13020,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        int: the integration ID

                        @@ -7904,18 +13030,18 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "enabled": true,
                        -  "title": "string",
                        -  "type": "string",
                        -  "activities": [
                        -    "string"
                        +  "enabled": true,
                        +  "title": "string",
                        +  "type": "string",
                        +  "activities": [
                        +    "string"
                           ],
                        -  "url": "string",
                        -  "token": "string",
                        -  "boardId": "string",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "userId": "string"
                        +  "url": "string",
                        +  "token": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                         }
                         

                        Responses

                        @@ -7948,53 +13074,33 @@ UserSecurity
                        # You can also use wget
                         curl -X PUT /api/boards/{board}/integrations/{int} \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        PUT /api/boards/{board}/integrations/{int} HTTP/1.1
                        +
                        PUT /api/boards/{board}/integrations/{int} HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations/{int}',
                        -  method: 'put',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "enabled": "string",
                        -  "title": "string",
                        -  "url": "string",
                        -  "token": "string",
                        -  "activities": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "enabled": "string",
                        +  "title": "string",
                        +  "url": "string",
                        +  "token": "string",
                        +  "activities": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations/{int}',
                        +fetch('/api/boards/{board}/integrations/{int}',
                         {
                        -  method: 'PUT',
                        +  method: 'PUT',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -8005,16 +13111,43 @@ fetch('/api/boards/{board}/integrations/{int}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "enabled": "string",
                        +  "title": "string",
                        +  "url": "string",
                        +  "token": "string",
                        +  "activities": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations/{int}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.put '/api/boards/{board}/integrations/{int}',
                        +result = RestClient.put '/api/boards/{board}/integrations/{int}',
                           params: {
                           }, headers: headers
                         
                        @@ -8023,21 +13156,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.put('/api/boards/{board}/integrations/{int}', params={
                        +r = requests.put('/api/boards/{board}/integrations/{int}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("PUT");
                        +con.setRequestMethod("PUT");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -8053,21 +13184,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("PUT", "/api/boards/{board}/integrations/{int}", data)
                        +    req, err := http.NewRequest("PUT", "/api/boards/{board}/integrations/{int}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -8075,6 +13205,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/boards/{board}/integrations/{int}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        PUT /api/boards/{board}/integrations/{int}

                        Edit integration data

                        @@ -8158,7 +13314,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        int: the integration ID

                        @@ -8168,7 +13324,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -8223,43 +13379,24 @@ UserSecurity
                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/integrations/{int} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/integrations/{int} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/integrations/{int} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations/{int}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations/{int}',
                        +fetch('/api/boards/{board}/integrations/{int}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -8270,15 +13407,35 @@ fetch('/api/boards/{board}/integrations/{int}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations/{int}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/integrations/{int}',
                        +result = RestClient.delete '/api/boards/{board}/integrations/{int}',
                           params: {
                           }, headers: headers
                         
                        @@ -8287,20 +13444,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/integrations/{int}', params={
                        +r = requests.delete('/api/boards/{board}/integrations/{int}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations/{int}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -8316,20 +13471,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/integrations/{int}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/integrations/{int}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -8337,6 +13491,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/integrations/{int}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/integrations/{int}

                        Delete integration

                        @@ -8368,7 +13547,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        int: the integration ID

                        @@ -8378,7 +13557,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -8426,45 +13605,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_int_activities

                        -

                        +

                        delete_integration_activities

                        +

                        Code samples

                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/integrations/{int}/activities \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/integrations/{int}/activities HTTP/1.1
                        +
                        DELETE /api/boards/{board}/integrations/{int}/activities HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations/{int}/activities',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations/{int}/activities',
                        +fetch('/api/boards/{board}/integrations/{int}/activities',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -8475,14 +13640,35 @@ fetch('/api/boards/{board}/integrations/{int}/activiti
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations/{int}/activities',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/integrations/{int}/activities',
                        +result = RestClient.delete '/api/boards/{board}/integrations/{int}/activities',
                           params: {
                           }, headers: headers
                         
                        @@ -8491,19 +13677,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/integrations/{int}/activities', params={
                        +r = requests.delete('/api/boards/{board}/integrations/{int}/activities', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations/{int}/activities");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations/{int}/activities");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -8519,19 +13704,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/integrations/{int}/activities", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/integrations/{int}/activities", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -8539,9 +13724,35 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/integrations/{int}/activities', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/integrations/{int}/activities

                        -

                        Parameters

                        +

                        Delete subscribed activities

                        +

                        Parameters

                        @@ -8558,18 +13769,42 @@ System.out.println(response.toString()); - + - +
                        path string truethe board valuethe board ID
                        int path string truethe int valuethe integration ID
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        int: the integration ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "enabled": true,
                        +  "title": "string",
                        +  "type": "string",
                        +  "activities": [
                        +    "string"
                        +  ],
                        +  "url": "string",
                        +  "token": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -8584,7 +13819,7 @@ System.out.println(response.toString()); - +
                        200 OK 200 responseNoneIntegrations
                        @@ -8592,52 +13827,36 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        post_board_int_activities

                        -

                        +

                        new_integration_activities

                        +

                        Code samples

                        # You can also use wget
                         curl -X POST /api/boards/{board}/integrations/{int}/activities \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/integrations/{int}/activities HTTP/1.1
                        +
                        POST /api/boards/{board}/integrations/{int}/activities HTTP/1.1
                         
                         Content-Type: multipart/form-data
                        +Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/integrations/{int}/activities',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "activities": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "activities": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/integrations/{int}/activities',
                        +fetch('/api/boards/{board}/integrations/{int}/activities',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -8648,15 +13867,39 @@ fetch('/api/boards/{board}/integrations/{int}/activiti
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "activities": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/integrations/{int}/activities',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/integrations/{int}/activities',
                        +result = RestClient.post '/api/boards/{board}/integrations/{int}/activities',
                           params: {
                           }, headers: headers
                         
                        @@ -8665,20 +13908,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/integrations/{int}/activities', params={
                        +r = requests.post('/api/boards/{board}/integrations/{int}/activities', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/integrations/{int}/activities");
                        +
                        URL obj = new URL("/api/boards/{board}/integrations/{int}/activities");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -8694,20 +13936,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/integrations/{int}/activities", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/integrations/{int}/activities", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -8715,15 +13957,42 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/integrations/{int}/activities', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/integrations/{int}/activities

                        +

                        Add subscribed activities

                        Body parameter

                        activities: string
                         
                         
                        -

                        Parameters

                        +

                        Parameters

                        @@ -8740,20 +14009,20 @@ System.out.println(response.toString()); - + - + - + @@ -8765,7 +14034,31 @@ System.out.println(response.toString());
                        path string truethe board valuethe board ID
                        int path string truethe int valuethe integration ID
                        body body objectfalsetrue none
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        int: the integration ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "enabled": true,
                        +  "title": "string",
                        +  "type": "string",
                        +  "activities": [
                        +    "string"
                        +  ],
                        +  "url": "string",
                        +  "token": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -8780,7 +14073,7 @@ System.out.println(response.toString()); - +
                        200 OK 200 responseNoneIntegrations
                        @@ -8796,43 +14089,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/lists \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/lists HTTP/1.1
                        +
                        GET /api/boards/{board}/lists HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/lists',
                        +fetch('/api/boards/{board}/lists',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -8843,15 +14117,35 @@ fetch('/api/boards/{board}/lists',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/lists',
                        +result = RestClient.get '/api/boards/{board}/lists',
                           params: {
                           }, headers: headers
                         
                        @@ -8860,20 +14154,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/lists', params={
                        +r = requests.get('/api/boards/{board}/lists', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/lists");
                        +
                        URL obj = new URL("/api/boards/{board}/lists");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -8889,20 +14181,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/lists", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/lists", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -8910,6 +14201,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/lists', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/lists

                        Get the list of Lists attached to a board

                        @@ -8934,7 +14250,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        Example responses

                        @@ -8944,8 +14260,8 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "title": "string"
                        +    "_id": "string",
                        +    "title": "string"
                           }
                         ]
                         
                        @@ -9008,49 +14324,29 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/lists \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/lists HTTP/1.1
                        +
                        POST /api/boards/{board}/lists HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "title": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "title": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/lists',
                        +fetch('/api/boards/{board}/lists',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -9061,16 +14357,39 @@ fetch('/api/boards/{board}/lists',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "title": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/lists',
                        +result = RestClient.post '/api/boards/{board}/lists',
                           params: {
                           }, headers: headers
                         
                        @@ -9079,21 +14398,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/lists', params={
                        +r = requests.post('/api/boards/{board}/lists', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/lists");
                        +
                        URL obj = new URL("/api/boards/{board}/lists");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -9109,21 +14426,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/lists", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/lists", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -9131,6 +14447,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/lists', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/lists

                        Add a List to a board

                        @@ -9163,7 +14505,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -9175,7 +14517,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        Example responses

                        @@ -9184,7 +14526,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -9239,43 +14581,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/lists/{list} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/lists/{list} HTTP/1.1
                        +
                        GET /api/boards/{board}/lists/{list} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/lists/{list}',
                        +fetch('/api/boards/{board}/lists/{list}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -9286,15 +14609,35 @@ fetch('/api/boards/{board}/lists/{list}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/lists/{list}',
                        +result = RestClient.get '/api/boards/{board}/lists/{list}',
                           params: {
                           }, headers: headers
                         
                        @@ -9303,20 +14646,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/lists/{list}', params={
                        +r = requests.get('/api/boards/{board}/lists/{list}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}");
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -9332,20 +14673,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -9353,6 +14693,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/lists/{list}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/lists/{list}

                        Get a List attached to a board

                        @@ -9384,7 +14749,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        list: the List ID

                        @@ -9394,22 +14759,23 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "title": "string",
                        -  "starred": true,
                        -  "archived": true,
                        -  "boardId": "string",
                        -  "swimlaneId": "string",
                        -  "createdAt": "string",
                        -  "sort": 0,
                        -  "updatedAt": "string",
                        -  "modifiedAt": "string",
                        -  "wipLimit": {
                        -    "value": 0,
                        -    "enabled": true,
                        -    "soft": true
                        +  "title": "string",
                        +  "starred": true,
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "boardId": "string",
                        +  "swimlaneId": "string",
                        +  "createdAt": "string",
                        +  "sort": 0,
                        +  "updatedAt": "string",
                        +  "modifiedAt": "string",
                        +  "wipLimit": {
                        +    "value": 0,
                        +    "enabled": true,
                        +    "soft": true
                           },
                        -  "color": "white",
                        -  "type": "string"
                        +  "color": "white",
                        +  "type": "string"
                         }
                         

                        Responses

                        @@ -9442,43 +14808,24 @@ UserSecurity
                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/lists/{list} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/lists/{list} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/lists/{list} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/lists/{list}',
                        +fetch('/api/boards/{board}/lists/{list}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -9489,15 +14836,35 @@ fetch('/api/boards/{board}/lists/{list}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/lists/{list}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/lists/{list}',
                        +result = RestClient.delete '/api/boards/{board}/lists/{list}',
                           params: {
                           }, headers: headers
                         
                        @@ -9506,20 +14873,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/lists/{list}', params={
                        +r = requests.delete('/api/boards/{board}/lists/{list}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}");
                        +
                        URL obj = new URL("/api/boards/{board}/lists/{list}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -9535,20 +14900,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/lists/{list}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/lists/{list}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -9556,6 +14920,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/lists/{list}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/lists/{list}

                        Delete a List

                        @@ -9589,7 +14978,7 @@ The list is not put in the recycle bin.

                        -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        list: the ID of the list to remove

                        @@ -9599,7 +14988,7 @@ The list is not put in the recycle bin.

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -9647,1396 +15036,6 @@ The list is not put in the recycle bin.

                        To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        Cards

                        -

                        get_all_cards

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X GET /api/boards/{board}/lists/{list}/cards \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        GET /api/boards/{board}/lists/{list}/cards HTTP/1.1
                        -
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}/cards',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -
                        -const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/lists/{list}/cards',
                        -{
                        -  method: 'GET',
                        -
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.get '/api/boards/{board}/lists/{list}/cards',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.get('/api/boards/{board}/lists/{list}/cards', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}/cards", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        GET /api/boards/{board}/lists/{list}/cards

                        -

                        Get all Cards attached to a List

                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID
                        listpathstringtruethe list ID
                        -

                        Detailed descriptions

                        -

                        board: the board ID

                        -

                        list: the list ID

                        -
                        -

                        Example responses

                        -
                        -
                        -

                        200 Response

                        -
                        -
                        [
                        -  {
                        -    "_id": "string",
                        -    "title": "string",
                        -    "description": "string"
                        -  }
                        -]
                        -
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        -

                        Response Schema

                        -

                        Status Code 200

                        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        » descriptionstringfalsenonenone
                        - -

                        new_card

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X POST /api/boards/{board}/lists/{list}/cards \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        POST /api/boards/{board}/lists/{list}/cards HTTP/1.1
                        -
                        -Content-Type: multipart/form-data
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}/cards',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "authorId": "string",
                        -  "members": "string",
                        -  "assignees": "string",
                        -  "title": "string",
                        -  "description": "string",
                        -  "swimlaneId": "string"
                        -}';
                        -const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/lists/{list}/cards',
                        -{
                        -  method: 'POST',
                        -  body: inputBody,
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.post '/api/boards/{board}/lists/{list}/cards',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.post('/api/boards/{board}/lists/{list}/cards', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/lists/{list}/cards", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        POST /api/boards/{board}/lists/{list}/cards

                        -

                        Create a new Card

                        -
                        -

                        Body parameter

                        -
                        -
                        authorId: string
                        -members: string
                        -assignees: string
                        -title: string
                        -description: string
                        -swimlaneId: string
                        -
                        -
                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board ID of the new card
                        listpathstringtruethe list ID of the new card
                        bodybodyobjectfalsenone
                        » authorIdbodystringtruethe authorId value
                        » membersbodystringfalsethe member IDs list of the new card
                        » assigneesbodystringfalsethe array of maximum one ID of assignee of the new card
                        » titlebodystringtruethe title of the new card
                        » descriptionbodystringtruethe description of the new card
                        » swimlaneIdbodystringtruethe swimlane ID of the new card
                        -

                        Detailed descriptions

                        -

                        board: the board ID of the new card

                        -

                        list: the list ID of the new card

                        -
                        -

                        Example responses

                        -
                        -
                        -

                        200 Response

                        -
                        -
                        {
                        -  "_id": "string"
                        -}
                        -
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        -

                        Response Schema

                        -

                        Status Code 200

                        - - - - - - - - - - - - - - - - - - - -
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        - -

                        get_board_list_card

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X GET /api/boards/{board}/lists/{list}/cards/{card} \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        GET /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        -
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -
                        -const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        -{
                        -  method: 'GET',
                        -
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.get '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.get('/api/boards/{board}/lists/{list}/cards/{card}', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        GET /api/boards/{board}/lists/{list}/cards/{card}

                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        listpathstringtruethe list value
                        cardpathstringtruethe card value
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseNone
                        - -

                        put_board_list_card

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X PUT /api/boards/{board}/lists/{list}/cards/{card} \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        PUT /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        -
                        -Content-Type: multipart/form-data
                        -
                        -
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  method: 'put',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "title": "string",
                        -  "listId": "string",
                        -  "authorId": "string",
                        -  "parentId": "string",
                        -  "description": "string",
                        -  "color": "string",
                        -  "labelIds": "string",
                        -  "requestedBy": "string",
                        -  "assignedBy": "string",
                        -  "receivedAt": "string",
                        -  "startAt": "string",
                        -  "dueAt": "string",
                        -  "endAt": "string",
                        -  "spentTime": "string",
                        -  "isOverTime": "string",
                        -  "customFields": "string",
                        -  "members": "string",
                        -  "assignees": "string",
                        -  "swimlaneId": "string"
                        -}';
                        -const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        -{
                        -  method: 'PUT',
                        -  body: inputBody,
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.put '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.put('/api/boards/{board}/lists/{list}/cards/{card}', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("PUT");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("PUT", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        PUT /api/boards/{board}/lists/{list}/cards/{card}

                        -
                        -

                        Body parameter

                        -
                        -
                        title: string
                        -listId: string
                        -authorId: string
                        -parentId: string
                        -description: string
                        -color: string
                        -labelIds: string
                        -requestedBy: string
                        -assignedBy: string
                        -receivedAt: string
                        -startAt: string
                        -dueAt: string
                        -endAt: string
                        -spentTime: string
                        -isOverTime: string
                        -customFields: string
                        -members: string
                        -assignees: string
                        -swimlaneId: string
                        -
                        -
                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        listpathstringtruethe list value
                        cardpathstringtruethe card value
                        bodybodyobjectfalsenone
                        » titlebodystringtruethe title value
                        » listIdbodystringtruethe listId value
                        » authorIdbodystringtruethe authorId value
                        » parentIdbodystringtruethe parentId value
                        » descriptionbodystringtruethe description value
                        » colorbodystringtruethe color value
                        » labelIdsbodystringtruethe labelIds value
                        » requestedBybodystringtruethe requestedBy value
                        » assignedBybodystringtruethe assignedBy value
                        » receivedAtbodystringtruethe receivedAt value
                        » startAtbodystringtruethe startAt value
                        » dueAtbodystringtruethe dueAt value
                        » endAtbodystringtruethe endAt value
                        » spentTimebodystringtruethe spentTime value
                        » isOverTimebodystringtruethe isOverTime value
                        » customFieldsbodystringtruethe customFields value
                        » membersbodystringtruethe members value
                        » assigneesbodystringtruethe assignees value
                        » swimlaneIdbodystringtruethe swimlaneId value
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseNone
                        - -

                        delete_board_list_card

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X DELETE /api/boards/{board}/lists/{list}/cards/{card} \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        DELETE /api/boards/{board}/lists/{list}/cards/{card} HTTP/1.1
                        -
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -
                        -const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/lists/{list}/cards/{card}',
                        -{
                        -  method: 'DELETE',
                        -
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.delete '/api/boards/{board}/lists/{list}/cards/{card}',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.delete('/api/boards/{board}/lists/{list}/cards/{card}', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/lists/{list}/cards/{card}");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/lists/{list}/cards/{card}", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        DELETE /api/boards/{board}/lists/{list}/cards/{card}

                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        listpathstringtruethe list value
                        cardpathstringtruethe card value
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseNone
                        - -

                        get_board_swimlane_cards

                        -

                        -
                        -

                        Code samples

                        -
                        -
                        # You can also use wget
                        -curl -X GET /api/boards/{board}/swimlanes/{swimlane}/cards \
                        -  -H 'Authorization: API_KEY'
                        -
                        -
                        -
                        GET /api/boards/{board}/swimlanes/{swimlane}/cards HTTP/1.1
                        -
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/swimlanes/{swimlane}/cards',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -
                        -const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -fetch('/api/boards/{board}/swimlanes/{swimlane}/cards',
                        -{
                        -  method: 'GET',
                        -
                        -  headers: headers
                        -})
                        -.then(function(res) {
                        -    return res.json();
                        -}).then(function(body) {
                        -    console.log(body);
                        -});
                        -
                        -
                        -
                        require 'rest-client'
                        -require 'json'
                        -
                        -headers = {
                        -  'Authorization' => 'API_KEY'
                        -}
                        -
                        -result = RestClient.get '/api/boards/{board}/swimlanes/{swimlane}/cards',
                        -  params: {
                        -  }, headers: headers
                        -
                        -p JSON.parse(result)
                        -
                        -
                        -
                        import requests
                        -headers = {
                        -  'Authorization': 'API_KEY'
                        -}
                        -
                        -r = requests.get('/api/boards/{board}/swimlanes/{swimlane}/cards', params={
                        -
                        -}, headers = headers)
                        -
                        -print r.json()
                        -
                        -
                        -
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}/cards");
                        -HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        -int responseCode = con.getResponseCode();
                        -BufferedReader in = new BufferedReader(
                        -    new InputStreamReader(con.getInputStream()));
                        -String inputLine;
                        -StringBuffer response = new StringBuffer();
                        -while ((inputLine = in.readLine()) != null) {
                        -    response.append(inputLine);
                        -}
                        -in.close();
                        -System.out.println(response.toString());
                        -
                        -
                        -
                        package main
                        -
                        -import (
                        -       "bytes"
                        -       "net/http"
                        -)
                        -
                        -func main() {
                        -
                        -    headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        -    }
                        -
                        -    data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes/{swimlane}/cards", data)
                        -    req.Header = headers
                        -
                        -    client := &http.Client{}
                        -    resp, err := client.Do(req)
                        -    // ...
                        -}
                        -
                        -
                        -

                        GET /api/boards/{board}/swimlanes/{swimlane}/cards

                        -

                        Parameters

                        - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        NameInTypeRequiredDescription
                        boardpathstringtruethe board value
                        swimlanepathstringtruethe swimlane value
                        -

                        Responses

                        - - - - - - - - - - - - - - - - - -
                        StatusMeaningDescriptionSchema
                        200OK200 responseNone
                        -

                        Users

                        add_board_member

                        @@ -11045,52 +15044,32 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/members/{user}/add \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/members/{user}/add HTTP/1.1
                        +
                        POST /api/boards/{board}/members/{user}/add HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/members/{user}/add',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "action": "string",
                        -  "isAdmin": true,
                        -  "isNoComments": true,
                        -  "isCommentOnly": true
                        -}';
                        +
                        const inputBody = '{
                        +  "action": "string",
                        +  "isAdmin": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/members/{user}/add',
                        +fetch('/api/boards/{board}/members/{user}/add',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -11101,16 +15080,42 @@ fetch('/api/boards/{board}/members/{user}/add',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "action": "string",
                        +  "isAdmin": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/members/{user}/add',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/members/{user}/add',
                        +result = RestClient.post '/api/boards/{board}/members/{user}/add',
                           params: {
                           }, headers: headers
                         
                        @@ -11119,21 +15124,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/members/{user}/add', params={
                        +r = requests.post('/api/boards/{board}/members/{user}/add', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/members/{user}/add");
                        +
                        URL obj = new URL("/api/boards/{board}/members/{user}/add");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -11149,21 +15152,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{user}/add", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{user}/add", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -11171,6 +15173,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/members/{user}/add', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/members/{user}/add

                        Add New Board Member with Role

                        @@ -11216,7 +15244,7 @@ to later change the permissions.

                        body body object -false +true none @@ -11249,7 +15277,7 @@ to later change the permissions.

                        -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the board ID

                        user: the user ID

                        @@ -11259,8 +15287,8 @@ to later change the permissions.

                        200 Response

                        {
                        -  "_id": "string",
                        -  "title": "string"
                        +  "_id": "string",
                        +  "title": "string"
                         }
                         

                        Responses

                        @@ -11315,52 +15343,36 @@ to later change the permissions.

                        To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        post_board_user_remove

                        -

                        +

                        remove_board_member

                        +

                        Code samples

                        # You can also use wget
                         curl -X POST /api/boards/{board}/members/{user}/remove \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/members/{user}/remove HTTP/1.1
                        +
                        POST /api/boards/{board}/members/{user}/remove HTTP/1.1
                         
                         Content-Type: multipart/form-data
                        +Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/members/{user}/remove',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "action": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "action": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/members/{user}/remove',
                        +fetch('/api/boards/{board}/members/{user}/remove',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -11371,15 +15383,39 @@ fetch('/api/boards/{board}/members/{user}/remove'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "action": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/members/{user}/remove',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/members/{user}/remove',
                        +result = RestClient.post '/api/boards/{board}/members/{user}/remove',
                           params: {
                           }, headers: headers
                         
                        @@ -11388,20 +15424,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/members/{user}/remove', params={
                        +r = requests.post('/api/boards/{board}/members/{user}/remove', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/members/{user}/remove");
                        +
                        URL obj = new URL("/api/boards/{board}/members/{user}/remove");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -11417,20 +15452,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{user}/remove", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/members/{user}/remove", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -11438,15 +15473,43 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/members/{user}/remove', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/members/{user}/remove

                        +

                        Remove Member from Board

                        +

                        Only the admin user (the first user) can call the REST API.

                        Body parameter

                        action: string
                         
                         
                        -

                        Parameters

                        +

                        Parameters

                        @@ -11463,20 +15526,20 @@ System.out.println(response.toString()); - + - + - + @@ -11484,11 +15547,25 @@ System.out.println(response.toString()); - +
                        path string truethe board valuethe board ID
                        user path string truethe user valuethe user ID
                        body body objectfalsetrue none
                        body string truethe action valuethe action (needs to be remove)
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the board ID

                        +

                        user: the user ID

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string",
                        +  "title": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -11503,7 +15580,36 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        » titlestringfalsenonenone
                        @@ -11511,50 +15617,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        get_current_user

                        -

                        +

                        create_user_token

                        +

                        Code samples

                        # You can also use wget
                        -curl -X GET /api/user \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +curl -X POST /api/createtoken/{user} \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/user HTTP/1.1
                        +
                        POST /api/createtoken/{user} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/user',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/user',
                        +fetch('/api/createtoken/{user}',
                         {
                        -  method: 'GET',
                        +  method: 'POST',
                         
                           headers: headers
                         })
                        @@ -11565,15 +15652,35 @@ fetch('/api/user',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/createtoken/{user}',
                        +{
                        +  method: 'POST',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/user',
                        +result = RestClient.post '/api/createtoken/{user}',
                           params: {
                           }, headers: headers
                         
                        @@ -11582,20 +15689,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/user', params={
                        +r = requests.post('/api/createtoken/{user}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/user");
                        +
                        URL obj = new URL("/api/createtoken/{user}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -11611,20 +15716,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/user", data)
                        +    req, err := http.NewRequest("POST", "/api/createtoken/{user}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -11632,6 +15736,257 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/createtoken/{user}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                        +
                        +

                        POST /api/createtoken/{user}

                        +

                        Create a user token

                        +

                        Only the admin user (the first user) can call the REST API.

                        +

                        Parameters

                        + + + + + + + + + + + + + + + + + + + +
                        NameInTypeRequiredDescription
                        userpathstringtruethe ID of the user to create token for.
                        +

                        Detailed descriptions

                        +

                        user: the ID of the user to create token for.

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        + + + + + + + + + + + + + + + + + +
                        StatusMeaningDescriptionSchema
                        200OK200 responseInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        + +

                        get_current_user

                        +

                        +
                        +

                        Code samples

                        +
                        +
                        # You can also use wget
                        +curl -X GET /api/user \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                        +
                        +
                        +
                        GET /api/user HTTP/1.1
                        +
                        +Accept: application/json
                        +
                        +
                        +
                        
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/user',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/user',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                        +
                        +headers = {
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                        +}
                        +
                        +result = RestClient.get '/api/user',
                        +  params: {
                        +  }, headers: headers
                        +
                        +p JSON.parse(result)
                        +
                        +
                        +
                        import requests
                        +headers = {
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                        +}
                        +
                        +r = requests.get('/api/user', headers = headers)
                        +
                        +print(r.json())
                        +
                        +
                        +
                        URL obj = new URL("/api/user");
                        +HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        +con.setRequestMethod("GET");
                        +int responseCode = con.getResponseCode();
                        +BufferedReader in = new BufferedReader(
                        +    new InputStreamReader(con.getInputStream()));
                        +String inputLine;
                        +StringBuffer response = new StringBuffer();
                        +while ((inputLine = in.readLine()) != null) {
                        +    response.append(inputLine);
                        +}
                        +in.close();
                        +System.out.println(response.toString());
                        +
                        +
                        +
                        package main
                        +
                        +import (
                        +       "bytes"
                        +       "net/http"
                        +)
                        +
                        +func main() {
                        +
                        +    headers := map[string][]string{
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                        +    }
                        +
                        +    data := bytes.NewBuffer([]byte{jsonReq})
                        +    req, err := http.NewRequest("GET", "/api/user", data)
                        +    req.Header = headers
                        +
                        +    client := &http.Client{}
                        +    resp, err := client.Do(req)
                        +    // ...
                        +}
                        +
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/user', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/user

                        returns the current user

                        @@ -11642,50 +15997,74 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "username": "string",
                        -  "emails": [
                        +  "username": "string",
                        +  "orgs": [
                             {
                        -      "address": "string",
                        -      "verified": true
                        +      "orgId": "string",
                        +      "orgDisplayName": "string"
                             }
                           ],
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "profile": {
                        -    "avatarUrl": "string",
                        -    "emailBuffer": [
                        -      "string"
                        +  "teams": [
                        +    {
                        +      "teamId": "string",
                        +      "teamDisplayName": "string"
                        +    }
                        +  ],
                        +  "emails": [
                        +    {
                        +      "address": "string",
                        +      "verified": true
                        +    }
                        +  ],
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "profile": {
                        +    "avatarUrl": "string",
                        +    "emailBuffer": [
                        +      "string"
                             ],
                        -    "fullname": "string",
                        -    "showDesktopDragHandles": true,
                        -    "hiddenSystemMessages": true,
                        -    "hiddenMinicardLabelText": true,
                        -    "initials": "string",
                        -    "invitedBoards": [
                        -      "string"
                        +    "fullname": "string",
                        +    "showDesktopDragHandles": true,
                        +    "hideCheckedItems": true,
                        +    "cardMaximized": true,
                        +    "hiddenSystemMessages": true,
                        +    "hiddenMinicardLabelText": true,
                        +    "initials": "string",
                        +    "invitedBoards": [
                        +      "string"
                             ],
                        -    "language": "string",
                        -    "notifications": [],
                        -    "activity": "string",
                        -    "read": "string",
                        -    "showCardsCountAt": 0,
                        -    "starredBoards": [
                        -      "string"
                        +    "language": "string",
                        +    "notifications": [
                        +      {
                        +        "activity": "string",
                        +        "read": "string"
                        +      }
                             ],
                        -    "icode": "string",
                        -    "boardView": "board-view-lists",
                        -    "listSortBy": "-modifiedat",
                        -    "templatesBoardId": "string",
                        -    "cardTemplatesSwimlaneId": "string",
                        -    "listTemplatesSwimlaneId": "string",
                        -    "boardTemplatesSwimlaneId": "string"
                        +    "showCardsCountAt": 0,
                        +    "startDayOfWeek": 0,
                        +    "starredBoards": [
                        +      "string"
                        +    ],
                        +    "icode": "string",
                        +    "boardView": "board-view-swimlanes",
                        +    "listSortBy": "-modifiedat",
                        +    "templatesBoardId": "string",
                        +    "cardTemplatesSwimlaneId": "string",
                        +    "listTemplatesSwimlaneId": "string",
                        +    "boardTemplatesSwimlaneId": "string"
                           },
                        -  "services": {},
                        -  "heartbeat": "string",
                        -  "isAdmin": true,
                        -  "createdThroughApi": true,
                        -  "loginDisabled": true,
                        -  "authenticationMethod": "string"
                        +  "services": {},
                        +  "heartbeat": "string",
                        +  "isAdmin": true,
                        +  "createdThroughApi": true,
                        +  "loginDisabled": true,
                        +  "authenticationMethod": "string",
                        +  "sessionData": {
                        +    "totalHits": 0
                        +  },
                        +  "importUsernames": [
                        +    "string"
                        +  ]
                         }
                         

                        Responses

                        @@ -11718,43 +16097,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/users \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/users HTTP/1.1
                        +
                        GET /api/users HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users',
                        +fetch('/api/users',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -11765,15 +16125,35 @@ fetch('/api/users',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/users',
                        +result = RestClient.get '/api/users',
                           params: {
                           }, headers: headers
                         
                        @@ -11782,20 +16162,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/users', params={
                        +r = requests.get('/api/users', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users");
                        +
                        URL obj = new URL("/api/users");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -11811,20 +16189,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/users", data)
                        +    req, err := http.NewRequest("GET", "/api/users", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -11832,6 +16209,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/users', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/users

                        return all the users

                        @@ -11844,8 +16246,8 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "username": "string"
                        +    "_id": "string",
                        +    "username": "string"
                           }
                         ]
                         
                        @@ -11908,51 +16310,31 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/users \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/users HTTP/1.1
                        +
                        POST /api/users HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "username": "string",
                        -  "email": "string",
                        -  "password": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "username": "string",
                        +  "email": "string",
                        +  "password": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users',
                        +fetch('/api/users',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -11963,16 +16345,41 @@ fetch('/api/users',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "username": "string",
                        +  "email": "string",
                        +  "password": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/users',
                        +result = RestClient.post '/api/users',
                           params: {
                           }, headers: headers
                         
                        @@ -11981,21 +16388,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/users', params={
                        +r = requests.post('/api/users', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users");
                        +
                        URL obj = new URL("/api/users");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -12011,21 +16416,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/users", data)
                        +    req, err := http.NewRequest("POST", "/api/users", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -12033,6 +16437,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/users', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/users

                        Create a new user

                        @@ -12061,7 +16491,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -12094,7 +16524,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -12149,43 +16579,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/users/{user} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/users/{user} HTTP/1.1
                        +
                        GET /api/users/{user} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users/{user}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users/{user}',
                        +fetch('/api/users/{user}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -12196,15 +16607,35 @@ fetch('/api/users/{user}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users/{user}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/users/{user}',
                        +result = RestClient.get '/api/users/{user}',
                           params: {
                           }, headers: headers
                         
                        @@ -12213,20 +16644,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/users/{user}', params={
                        +r = requests.get('/api/users/{user}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users/{user}");
                        +
                        URL obj = new URL("/api/users/{user}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -12242,20 +16671,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/users/{user}", data)
                        +    req, err := http.NewRequest("GET", "/api/users/{user}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -12263,6 +16691,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/users/{user}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/users/{user}

                        get a given user

                        @@ -12284,12 +16737,12 @@ System.out.println(response.toString()); path string true -the user ID +the user ID or username -

                        Detailed descriptions

                        -

                        user: the user ID

                        +

                        Detailed descriptions

                        +

                        user: the user ID or username

                        Example responses

                        @@ -12297,50 +16750,74 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "username": "string",
                        -  "emails": [
                        +  "username": "string",
                        +  "orgs": [
                             {
                        -      "address": "string",
                        -      "verified": true
                        +      "orgId": "string",
                        +      "orgDisplayName": "string"
                             }
                           ],
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "profile": {
                        -    "avatarUrl": "string",
                        -    "emailBuffer": [
                        -      "string"
                        +  "teams": [
                        +    {
                        +      "teamId": "string",
                        +      "teamDisplayName": "string"
                        +    }
                        +  ],
                        +  "emails": [
                        +    {
                        +      "address": "string",
                        +      "verified": true
                        +    }
                        +  ],
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "profile": {
                        +    "avatarUrl": "string",
                        +    "emailBuffer": [
                        +      "string"
                             ],
                        -    "fullname": "string",
                        -    "showDesktopDragHandles": true,
                        -    "hiddenSystemMessages": true,
                        -    "hiddenMinicardLabelText": true,
                        -    "initials": "string",
                        -    "invitedBoards": [
                        -      "string"
                        +    "fullname": "string",
                        +    "showDesktopDragHandles": true,
                        +    "hideCheckedItems": true,
                        +    "cardMaximized": true,
                        +    "hiddenSystemMessages": true,
                        +    "hiddenMinicardLabelText": true,
                        +    "initials": "string",
                        +    "invitedBoards": [
                        +      "string"
                             ],
                        -    "language": "string",
                        -    "notifications": [],
                        -    "activity": "string",
                        -    "read": "string",
                        -    "showCardsCountAt": 0,
                        -    "starredBoards": [
                        -      "string"
                        +    "language": "string",
                        +    "notifications": [
                        +      {
                        +        "activity": "string",
                        +        "read": "string"
                        +      }
                             ],
                        -    "icode": "string",
                        -    "boardView": "board-view-lists",
                        -    "listSortBy": "-modifiedat",
                        -    "templatesBoardId": "string",
                        -    "cardTemplatesSwimlaneId": "string",
                        -    "listTemplatesSwimlaneId": "string",
                        -    "boardTemplatesSwimlaneId": "string"
                        +    "showCardsCountAt": 0,
                        +    "startDayOfWeek": 0,
                        +    "starredBoards": [
                        +      "string"
                        +    ],
                        +    "icode": "string",
                        +    "boardView": "board-view-swimlanes",
                        +    "listSortBy": "-modifiedat",
                        +    "templatesBoardId": "string",
                        +    "cardTemplatesSwimlaneId": "string",
                        +    "listTemplatesSwimlaneId": "string",
                        +    "boardTemplatesSwimlaneId": "string"
                           },
                        -  "services": {},
                        -  "heartbeat": "string",
                        -  "isAdmin": true,
                        -  "createdThroughApi": true,
                        -  "loginDisabled": true,
                        -  "authenticationMethod": "string"
                        +  "services": {},
                        +  "heartbeat": "string",
                        +  "isAdmin": true,
                        +  "createdThroughApi": true,
                        +  "loginDisabled": true,
                        +  "authenticationMethod": "string",
                        +  "sessionData": {
                        +    "totalHits": 0
                        +  },
                        +  "importUsernames": [
                        +    "string"
                        +  ]
                         }
                         

                        Responses

                        @@ -12373,49 +16850,29 @@ UserSecurity
                        # You can also use wget
                         curl -X PUT /api/users/{user} \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        PUT /api/users/{user} HTTP/1.1
                        +
                        PUT /api/users/{user} HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users/{user}',
                        -  method: 'put',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "action": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "action": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users/{user}',
                        +fetch('/api/users/{user}',
                         {
                        -  method: 'PUT',
                        +  method: 'PUT',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -12426,16 +16883,39 @@ fetch('/api/users/{user}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "action": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users/{user}',
                        +{
                        +  method: 'PUT',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.put '/api/users/{user}',
                        +result = RestClient.put '/api/users/{user}',
                           params: {
                           }, headers: headers
                         
                        @@ -12444,21 +16924,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.put('/api/users/{user}', params={
                        +r = requests.put('/api/users/{user}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users/{user}");
                        +
                        URL obj = new URL("/api/users/{user}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("PUT");
                        +con.setRequestMethod("PUT");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -12474,21 +16952,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("PUT", "/api/users/{user}", data)
                        +    req, err := http.NewRequest("PUT", "/api/users/{user}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -12496,6 +16973,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('PUT','/api/users/{user}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        PUT /api/users/{user}

                        edit a given user

                        @@ -12535,7 +17038,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -12547,7 +17050,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        user: the user ID

                        Example responses

                        @@ -12556,8 +17059,8 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string",
                        -  "title": "string"
                        +  "_id": "string",
                        +  "title": "string"
                         }
                         

                        Responses

                        @@ -12619,43 +17122,24 @@ UserSecurity
                        # You can also use wget
                         curl -X DELETE /api/users/{user} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/users/{user} HTTP/1.1
                        +
                        DELETE /api/users/{user} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/users/{user}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/users/{user}',
                        +fetch('/api/users/{user}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -12666,15 +17150,35 @@ fetch('/api/users/{user}',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/users/{user}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/users/{user}',
                        +result = RestClient.delete '/api/users/{user}',
                           params: {
                           }, headers: headers
                         
                        @@ -12683,20 +17187,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/users/{user}', params={
                        +r = requests.delete('/api/users/{user}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/users/{user}");
                        +
                        URL obj = new URL("/api/users/{user}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -12712,20 +17214,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/users/{user}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/users/{user}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -12733,6 +17234,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/users/{user}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/users/{user}

                        Delete a user

                        @@ -12758,7 +17284,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        user: the ID of the user to delete

                        Example responses

                        @@ -12767,7 +17293,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -12823,43 +17349,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/swimlanes \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/swimlanes HTTP/1.1
                        +
                        GET /api/boards/{board}/swimlanes HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/swimlanes',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/swimlanes',
                        +fetch('/api/boards/{board}/swimlanes',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -12870,15 +17377,35 @@ fetch('/api/boards/{board}/swimlanes',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/swimlanes',
                        +result = RestClient.get '/api/boards/{board}/swimlanes',
                           params: {
                           }, headers: headers
                         
                        @@ -12887,20 +17414,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/swimlanes', params={
                        +r = requests.get('/api/boards/{board}/swimlanes', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/swimlanes");
                        +
                        URL obj = new URL("/api/boards/{board}/swimlanes");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -12916,20 +17441,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -12937,6 +17461,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/swimlanes', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/swimlanes

                        Get the list of swimlanes attached to a board

                        @@ -12961,7 +17510,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board

                        Example responses

                        @@ -12971,8 +17520,8 @@ System.out.println(response.toString());
                        [
                           {
                        -    "_id": "string",
                        -    "title": "string"
                        +    "_id": "string",
                        +    "title": "string"
                           }
                         ]
                         
                        @@ -13035,49 +17584,29 @@ UserSecurity
                        # You can also use wget
                         curl -X POST /api/boards/{board}/swimlanes \
                        -  -H 'Content-Type: multipart/form-data' \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Content-Type: multipart/form-data' \
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        POST /api/boards/{board}/swimlanes HTTP/1.1
                        +
                        POST /api/boards/{board}/swimlanes HTTP/1.1
                         
                         Content-Type: multipart/form-data
                         Accept: application/json
                         
                         
                        -
                        var headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/swimlanes',
                        -  method: 'post',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        -const inputBody = '{
                        -  "title": "string"
                        -}';
                        +
                        const inputBody = '{
                        +  "title": "string"
                        +}';
                         const headers = {
                        -  'Content-Type':'multipart/form-data',
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/swimlanes',
                        +fetch('/api/boards/{board}/swimlanes',
                         {
                        -  method: 'POST',
                        +  method: 'POST',
                           body: inputBody,
                           headers: headers
                         })
                        @@ -13088,16 +17617,39 @@ fetch('/api/boards/{board}/swimlanes',
                         });
                         
                         
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +const inputBody = {
                        +  "title": "string"
                        +};
                        +const headers = {
                        +  'Content-Type':'multipart/form-data',
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes',
                        +{
                        +  method: 'POST',
                        +  body: JSON.stringify(inputBody),
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Content-Type' => 'multipart/form-data',
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Content-Type' => 'multipart/form-data',
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.post '/api/boards/{board}/swimlanes',
                        +result = RestClient.post '/api/boards/{board}/swimlanes',
                           params: {
                           }, headers: headers
                         
                        @@ -13106,21 +17658,19 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Content-Type': 'multipart/form-data',
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Content-Type': 'multipart/form-data',
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.post('/api/boards/{board}/swimlanes', params={
                        +r = requests.post('/api/boards/{board}/swimlanes', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/swimlanes");
                        +
                        URL obj = new URL("/api/boards/{board}/swimlanes");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("POST");
                        +con.setRequestMethod("POST");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -13136,21 +17686,20 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Content-Type": []string{"multipart/form-data"},
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Content-Type": []string{"multipart/form-data"},
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("POST", "/api/boards/{board}/swimlanes", data)
                        +    req, err := http.NewRequest("POST", "/api/boards/{board}/swimlanes", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -13158,6 +17707,32 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'multipart/form-data',
                        +    'Accept' => 'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('POST','/api/boards/{board}/swimlanes', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        POST /api/boards/{board}/swimlanes

                        Add a swimlane to a board

                        @@ -13190,7 +17765,7 @@ System.out.println(response.toString()); body body object -false +true none @@ -13202,7 +17777,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board

                        Example responses

                        @@ -13211,7 +17786,7 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "_id": "string"
                        +  "_id": "string"
                         }
                         

                        Responses

                        @@ -13266,43 +17841,24 @@ UserSecurity
                        # You can also use wget
                         curl -X GET /api/boards/{board}/swimlanes/{swimlane} \
                        -  -H 'Accept: application/json' \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        GET /api/boards/{board}/swimlanes/{swimlane} HTTP/1.1
                        +
                        GET /api/boards/{board}/swimlanes/{swimlane} HTTP/1.1
                         
                        -Accept: application/json
                        -
                        -
                        -
                        var headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/swimlanes/{swimlane}',
                        -  method: 'get',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Accept':'application/json',
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/swimlanes/{swimlane}',
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}',
                         {
                        -  method: 'GET',
                        +  method: 'GET',
                         
                           headers: headers
                         })
                        @@ -13313,15 +17869,35 @@ fetch('/api/boards/{board}/swimlanes/{swimlane}'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}',
                        +{
                        +  method: 'GET',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Accept' => 'application/json',
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.get '/api/boards/{board}/swimlanes/{swimlane}',
                        +result = RestClient.get '/api/boards/{board}/swimlanes/{swimlane}',
                           params: {
                           }, headers: headers
                         
                        @@ -13330,20 +17906,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Accept': 'application/json',
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.get('/api/boards/{board}/swimlanes/{swimlane}', params={
                        +r = requests.get('/api/boards/{board}/swimlanes/{swimlane}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}");
                        +
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("GET");
                        +con.setRequestMethod("GET");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -13359,20 +17933,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Accept": []string{"application/json"},
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes/{swimlane}", data)
                        +    req, err := http.NewRequest("GET", "/api/boards/{board}/swimlanes/{swimlane}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -13380,6 +17953,31 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('GET','/api/boards/{board}/swimlanes/{swimlane}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        GET /api/boards/{board}/swimlanes/{swimlane}

                        Get a swimlane

                        @@ -13411,7 +18009,7 @@ System.out.println(response.toString()); -

                        Detailed descriptions

                        +

                        Detailed descriptions

                        board: the ID of the board

                        swimlane: the ID of the swimlane

                        @@ -13421,15 +18019,16 @@ System.out.println(response.toString());

                        200 Response

                        {
                        -  "title": "string",
                        -  "archived": true,
                        -  "boardId": "string",
                        -  "createdAt": "string",
                        -  "sort": 0,
                        -  "color": "white",
                        -  "updatedAt": "string",
                        -  "modifiedAt": "string",
                        -  "type": "string"
                        +  "title": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "sort": 0,
                        +  "color": "white",
                        +  "updatedAt": "string",
                        +  "modifiedAt": "string",
                        +  "type": "string"
                         }
                         

                        Responses

                        @@ -13455,45 +18054,31 @@ System.out.println(response.toString()); To perform this operation, you must be authenticated by means of one of the following methods: UserSecurity -

                        delete_board_swimlane

                        -

                        +

                        delete_swimlane

                        +

                        Code samples

                        # You can also use wget
                         curl -X DELETE /api/boards/{board}/swimlanes/{swimlane} \
                        -  -H 'Authorization: API_KEY'
                        +  -H 'Accept: application/json' \
                        +  -H 'Authorization: API_KEY'
                         
                         
                        -
                        DELETE /api/boards/{board}/swimlanes/{swimlane} HTTP/1.1
                        +
                        DELETE /api/boards/{board}/swimlanes/{swimlane} HTTP/1.1
                         
                        -
                        -
                        var headers = {
                        -  'Authorization':'API_KEY'
                        -
                        -};
                        -
                        -$.ajax({
                        -  url: '/api/boards/{board}/swimlanes/{swimlane}',
                        -  method: 'delete',
                        -
                        -  headers: headers,
                        -  success: function(data) {
                        -    console.log(JSON.stringify(data));
                        -  }
                        -})
                        -
                        -
                        -
                        const fetch = require('node-fetch');
                        +Accept: application/json
                         
                        +
                        +
                        
                         const headers = {
                        -  'Authorization':'API_KEY'
                        -
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                         };
                         
                        -fetch('/api/boards/{board}/swimlanes/{swimlane}',
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}',
                         {
                        -  method: 'DELETE',
                        +  method: 'DELETE',
                         
                           headers: headers
                         })
                        @@ -13504,14 +18089,35 @@ fetch('/api/boards/{board}/swimlanes/{swimlane}'
                        -
                        require 'rest-client'
                        -require 'json'
                        +
                        const fetch = require('node-fetch');
                        +
                        +const headers = {
                        +  'Accept':'application/json',
                        +  'Authorization':'API_KEY'
                        +};
                        +
                        +fetch('/api/boards/{board}/swimlanes/{swimlane}',
                        +{
                        +  method: 'DELETE',
                        +
                        +  headers: headers
                        +})
                        +.then(function(res) {
                        +    return res.json();
                        +}).then(function(body) {
                        +    console.log(body);
                        +});
                        +
                        +
                        +
                        require 'rest-client'
                        +require 'json'
                         
                         headers = {
                        -  'Authorization' => 'API_KEY'
                        +  'Accept' => 'application/json',
                        +  'Authorization' => 'API_KEY'
                         }
                         
                        -result = RestClient.delete '/api/boards/{board}/swimlanes/{swimlane}',
                        +result = RestClient.delete '/api/boards/{board}/swimlanes/{swimlane}',
                           params: {
                           }, headers: headers
                         
                        @@ -13520,19 +18126,18 @@ p JSON.parse(result)
                         
                        import requests
                         headers = {
                        -  'Authorization': 'API_KEY'
                        +  'Accept': 'application/json',
                        +  'Authorization': 'API_KEY'
                         }
                         
                        -r = requests.delete('/api/boards/{board}/swimlanes/{swimlane}', params={
                        +r = requests.delete('/api/boards/{board}/swimlanes/{swimlane}', headers = headers)
                         
                        -}, headers = headers)
                        -
                        -print r.json()
                        +print(r.json())
                         
                         
                        -
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}");
                        +
                        URL obj = new URL("/api/boards/{board}/swimlanes/{swimlane}");
                         HttpURLConnection con = (HttpURLConnection) obj.openConnection();
                        -con.setRequestMethod("DELETE");
                        +con.setRequestMethod("DELETE");
                         int responseCode = con.getResponseCode();
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(con.getInputStream()));
                        @@ -13548,19 +18153,19 @@ System.out.println(response.toString());
                         
                        package main
                         
                         import (
                        -       "bytes"
                        -       "net/http"
                        +       "bytes"
                        +       "net/http"
                         )
                         
                         func main() {
                         
                             headers := map[string][]string{
                        -        "Authorization": []string{"API_KEY"},
                        -        
                        +        "Accept": []string{"application/json"},
                        +        "Authorization": []string{"API_KEY"},
                             }
                         
                             data := bytes.NewBuffer([]byte{jsonReq})
                        -    req, err := http.NewRequest("DELETE", "/api/boards/{board}/swimlanes/{swimlane}", data)
                        +    req, err := http.NewRequest("DELETE", "/api/boards/{board}/swimlanes/{swimlane}", data)
                             req.Header = headers
                         
                             client := &http.Client{}
                        @@ -13568,9 +18173,36 @@ System.out.println(response.toString());
                             // ...
                         }
                         
                        +
                        +
                         'application/json',
                        +    'Authorization' => 'API_KEY',
                        +);
                        +
                        +$client = new \GuzzleHttp\Client();
                        +
                        +// Define array of request body.
                        +$request_body = array();
                        +
                        +try {
                        +    $response = $client->request('DELETE','/api/boards/{board}/swimlanes/{swimlane}', array(
                        +        'headers' => $headers,
                        +        'json' => $request_body,
                        +       )
                        +    );
                        +    print_r($response->getBody()->getContents());
                        + }
                        + catch (\GuzzleHttp\Exception\BadResponseException $e) {
                        +    // handle exception or api errors.
                        +    print_r($e->getMessage());
                        + }
                        +
                        + // ...
                        +
                         

                        DELETE /api/boards/{board}/swimlanes/{swimlane}

                        -

                        Parameters

                        +

                        Delete a swimlane

                        +

                        The swimlane will be deleted, not moved to the recycle bin

                        +

                        Parameters

                        @@ -13587,18 +18219,31 @@ System.out.println(response.toString()); - + - +
                        path string truethe board valuethe ID of the board
                        swimlane path string truethe swimlane valuethe ID of the swimlane
                        -

                        Responses

                        +

                        Detailed descriptions

                        +

                        board: the ID of the board

                        +

                        swimlane: the ID of the swimlane

                        +
                        +

                        Example responses

                        +
                        +
                        +

                        200 Response

                        +
                        +
                        {
                        +  "_id": "string"
                        +}
                        +
                        +

                        Responses

                        @@ -13613,7 +18258,29 @@ System.out.println(response.toString()); - + + + +
                        200 OK 200 responseNoneInline
                        +

                        Response Schema

                        +

                        Status Code 200

                        + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        » _idstringfalsenonenone
                        @@ -13622,66 +18289,87 @@ To perform this operation, you must be authenticated by means of one of the foll UserSecurity

                        Schemas

                        -

                        Boards

                        -

                        +

                        Boards

                        +

                        + + +

                        {
                        -  "title": "string",
                        -  "slug": "string",
                        -  "archived": true,
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "stars": 0,
                        -  "labels": [
                        +  "title": "string",
                        +  "slug": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "stars": 0,
                        +  "labels": [
                             {
                        -      "_id": "string",
                        -      "name": "string",
                        -      "color": "green"
                        +      "_id": "string",
                        +      "name": "string",
                        +      "color": "white"
                             }
                           ],
                        -  "members": [
                        +  "members": [
                             {
                        -      "userId": "string",
                        -      "isAdmin": true,
                        -      "isActive": true,
                        -      "isNoComments": true,
                        -      "isCommentOnly": true,
                        -      "isWorker": true
                        +      "userId": "string",
                        +      "isAdmin": true,
                        +      "isActive": true,
                        +      "isNoComments": true,
                        +      "isCommentOnly": true,
                        +      "isWorker": true
                             }
                           ],
                        -  "permission": "public",
                        -  "color": "belize",
                        -  "description": "string",
                        -  "subtasksDefaultBoardId": "string",
                        -  "subtasksDefaultListId": "string",
                        -  "dateSettingsDefaultBoardId": "string",
                        -  "dateSettingsDefaultListId": "string",
                        -  "allowsSubtasks": true,
                        -  "allowsAttachments": true,
                        -  "allowsChecklists": true,
                        -  "allowsComments": true,
                        -  "allowsDescriptionTitle": true,
                        -  "allowsDescriptionText": true,
                        -  "allowsActivities": true,
                        -  "allowsLabels": true,
                        -  "allowsAssignee": true,
                        -  "allowsMembers": true,
                        -  "allowsRequestedBy": true,
                        -  "allowsAssignedBy": true,
                        -  "allowsReceivedDate": true,
                        -  "allowsStartDate": true,
                        -  "allowsEndDate": true,
                        -  "allowsDueDate": true,
                        -  "presentParentTask": "prefix-with-full-path",
                        -  "startAt": "string",
                        -  "dueAt": "string",
                        -  "endAt": "string",
                        -  "spentTime": 0,
                        -  "isOvertime": true,
                        -  "type": "string"
                        +  "permission": "public",
                        +  "orgs": [
                        +    {
                        +      "orgId": "string",
                        +      "orgDisplayName": "string",
                        +      "isActive": true
                        +    }
                        +  ],
                        +  "teams": [
                        +    {
                        +      "teamId": "string",
                        +      "teamDisplayName": "string",
                        +      "isActive": true
                        +    }
                        +  ],
                        +  "color": "belize",
                        +  "description": "string",
                        +  "subtasksDefaultBoardId": "string",
                        +  "subtasksDefaultListId": "string",
                        +  "dateSettingsDefaultBoardId": "string",
                        +  "dateSettingsDefaultListId": "string",
                        +  "allowsSubtasks": true,
                        +  "allowsAttachments": true,
                        +  "allowsChecklists": true,
                        +  "allowsComments": true,
                        +  "allowsDescriptionTitle": true,
                        +  "allowsDescriptionText": true,
                        +  "allowsActivities": true,
                        +  "allowsLabels": true,
                        +  "allowsCreator": true,
                        +  "allowsAssignee": true,
                        +  "allowsMembers": true,
                        +  "allowsRequestedBy": true,
                        +  "allowsCardSortingByNumber": true,
                        +  "allowsAssignedBy": true,
                        +  "allowsReceivedDate": true,
                        +  "allowsStartDate": true,
                        +  "allowsEndDate": true,
                        +  "allowsDueDate": true,
                        +  "presentParentTask": "prefix-with-full-path",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": 0,
                        +  "isOvertime": true,
                        +  "type": "board",
                        +  "sort": 0
                         }
                         
                         
                        -

                        This is a Board.

                        +

                        This is a Board.

                        Properties

                        @@ -13716,6 +18404,13 @@ UserSecurity + + + + + + + @@ -13724,7 +18419,7 @@ UserSecurity - + @@ -13738,8 +18433,8 @@ UserSecurity - - + + @@ -13758,6 +18453,20 @@ UserSecurity + + + + + + + + + + + + + + @@ -13766,35 +18475,35 @@ UserSecurity - + - + - + - + - + @@ -13856,6 +18565,13 @@ UserSecurity + + + + + + + @@ -13877,6 +18593,13 @@ UserSecurity + + + + + + + @@ -13913,42 +18636,42 @@ UserSecurity - + - + - + - + - + - + - + @@ -13958,7 +18681,14 @@ UserSecurity - + + + + + + + +
                        Is the board archived?
                        archivedAtstring¦nullfalsenoneLatest archiving time of the board
                        createdAt string true
                        modifiedAtstring|nullstring¦null false none Last modification time of the board
                        labels[BoardsLabels]true[BoardsLabels]¦nullfalse none List of labels attached to a board
                        visibility of the board
                        orgs[BoardsOrgs]¦nullfalsenonethe list of organizations that a board belongs to
                        teams[BoardsTeams]¦nullfalsenonethe list of teams that a board belongs to
                        color string true
                        descriptionstring|nullstring¦null false none The description of the board
                        subtasksDefaultBoardIdstring|nullstring¦null false none The default board ID assigned to subtasks.
                        subtasksDefaultListIdstring|nullstring¦null false none The default List ID assigned to subtasks.
                        dateSettingsDefaultBoardIdstring|nullstring¦null false none none
                        dateSettingsDefaultListIdstring|nullstring¦null false none noneDoes the board allows labels?
                        allowsCreatorbooleantruenoneDoes the board allow creator?
                        allowsAssignee boolean trueDoes the board allows requested by?
                        allowsCardSortingByNumberbooleantruenoneDoes the board allows card sorting by number?
                        allowsAssignedBy boolean true
                        presentParentTaskstring|nullstring¦null false noneControls how to present the parent task: - prefix-with-full-path: add a prefix with the full path - prefix-with-parent: add a prefisx with the parent name - subtext-with-full-path: add a subtext with the full path - subtext-with-parent: add a subtext with the parent name - no-parent: does not show the parent at allControls how to present the parent task:

                        - prefix-with-full-path: add a prefix with the full path
                        - prefix-with-parent: add a prefisx with the parent name
                        - subtext-with-full-path: add a subtext with the full path
                        - subtext-with-parent: add a subtext with the parent name
                        - no-parent: does not show the parent at all
                        startAtstring|nullstring¦null false none Starting date of the board.
                        dueAtstring|nullstring¦null false none Due date of the board.
                        endAtstring|nullstring¦null false none End date of the board.
                        spentTimenumber|nullnumber¦null false none Time spent in the board.
                        isOvertimeboolean|nullboolean¦null false none Is the board overtimed?string true noneThe type of boardThe type of board
                        possible values: board, template-board, template-container
                        sortnumbertruenoneSort value
                        @@ -14028,6 +18758,22 @@ UserSecurity corteza +color +clearblue + + +color +natural + + +color +modern + + +color +moderndark + + presentParentTask prefix-with-full-path @@ -14047,18 +18793,33 @@ UserSecurity presentParentTask no-parent + +type +board + + +type +template-board + + +type +template-container + -

                        BoardsLabels

                        -

                        +

                        BoardsLabels

                        +

                        + + +

                        {
                        -  "_id": "string",
                        -  "name": "string",
                        -  "color": "green"
                        +  "_id": "string",
                        +  "name": "string",
                        +  "color": "white"
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -14089,11 +18850,11 @@ UserSecurity - +
                        string true nonecolor of a label. Can be amongst green, yellow, orange, red, purple, blue, sky, lime, pink, black, silver, peachpuff, crimson, plum, darkgreen, slateblue, magenta, gold, navy, gray, saddlebrown, paleturquoise, mistyrose, indigocolor of a label.

                        Can be amongst green, yellow, orange, red, purple,
                        blue, sky, lime, pink, black,
                        silver, peachpuff, crimson, plum, darkgreen,
                        slateblue, magenta, gold, navy, gray,
                        saddlebrown, paleturquoise, mistyrose, indigo
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -14104,6 +18865,10 @@ UserSecurity + + + + @@ -14200,19 +18965,22 @@ UserSecurity
                        colorwhite
                        color green
                        -

                        BoardsMembers

                        -

                        +

                        BoardsMembers

                        +

                        + + +

                        {
                        -  "userId": "string",
                        -  "isAdmin": true,
                        -  "isActive": true,
                        -  "isNoComments": true,
                        -  "isCommentOnly": true,
                        -  "isWorker": true
                        +  "userId": "string",
                        +  "isAdmin": true,
                        +  "isActive": true,
                        +  "isNoComments": true,
                        +  "isCommentOnly": true,
                        +  "isWorker": true
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -14268,20 +19036,117 @@ UserSecurity
                        -

                        CardComments

                        -

                        +

                        BoardsOrgs

                        +

                        + + +

                        {
                        -  "boardId": "string",
                        -  "cardId": "string",
                        -  "text": "string",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "userId": "string"
                        +  "orgId": "string",
                        +  "orgDisplayName": "string",
                        +  "isActive": true
                         }
                         
                         
                        -

                        A comment on a card

                        -

                        Properties

                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        orgIdstringtruenoneThe uniq ID of the organization
                        orgDisplayNamestringtruenoneThe display name of the organization
                        isActivebooleantruenoneIs the organization active?
                        +

                        BoardsTeams

                        +

                        + + +

                        +
                        {
                        +  "teamId": "string",
                        +  "teamDisplayName": "string",
                        +  "isActive": true
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        teamIdstringtruenoneThe uniq ID of the team
                        teamDisplayNamestringtruenoneThe display name of the team
                        isActivebooleantruenoneIs the team active?
                        +

                        CardComments

                        +

                        + + +

                        +
                        {
                        +  "boardId": "string",
                        +  "cardId": "string",
                        +  "text": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                        +}
                        +
                        +
                        +

                        A comment on a card

                        +

                        Properties

                        @@ -14337,50 +19202,111 @@ UserSecurity
                        -

                        Cards

                        -

                        +

                        Cards

                        +

                        + + +

                        {
                        -  "title": "string",
                        -  "archived": true,
                        -  "parentId": "string",
                        -  "listId": "string",
                        -  "swimlaneId": "string",
                        -  "boardId": "string",
                        -  "coverId": "string",
                        -  "color": "white",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "customFields": [
                        +  "title": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "parentId": "string",
                        +  "listId": "string",
                        +  "swimlaneId": "string",
                        +  "boardId": "string",
                        +  "coverId": "string",
                        +  "color": "white",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "customFields": [
                             {}
                           ],
                        -  "dateLastActivity": "string",
                        -  "description": "string",
                        -  "requestedBy": "string",
                        -  "assignedBy": "string",
                        -  "labelIds": [
                        -    "string"
                        +  "dateLastActivity": "string",
                        +  "description": "string",
                        +  "requestedBy": "string",
                        +  "assignedBy": "string",
                        +  "labelIds": [
                        +    "string"
                           ],
                        -  "members": [
                        -    "string"
                        +  "members": [
                        +    "string"
                           ],
                        -  "assignees": [
                        -    "string"
                        +  "assignees": [
                        +    "string"
                           ],
                        -  "receivedAt": "string",
                        -  "startAt": "string",
                        -  "dueAt": "string",
                        -  "endAt": "string",
                        -  "spentTime": 0,
                        -  "isOvertime": true,
                        -  "userId": "string",
                        -  "sort": 0,
                        -  "subtaskSort": 0,
                        -  "type": "string",
                        -  "linkedId": "string"
                        +  "receivedAt": "string",
                        +  "startAt": "string",
                        +  "dueAt": "string",
                        +  "endAt": "string",
                        +  "spentTime": 0,
                        +  "isOvertime": true,
                        +  "userId": "string",
                        +  "sort": 0,
                        +  "subtaskSort": 0,
                        +  "type": "string",
                        +  "linkedId": "string",
                        +  "vote": {
                        +    "question": "string",
                        +    "positive": [
                        +      "string"
                        +    ],
                        +    "negative": [
                        +      "string"
                        +    ],
                        +    "end": "string",
                        +    "public": true,
                        +    "allowNonBoardMembers": true
                        +  },
                        +  "poker": {
                        +    "question": true,
                        +    "one": [
                        +      "string"
                        +    ],
                        +    "two": [
                        +      "string"
                        +    ],
                        +    "three": [
                        +      "string"
                        +    ],
                        +    "five": [
                        +      "string"
                        +    ],
                        +    "eight": [
                        +      "string"
                        +    ],
                        +    "thirteen": [
                        +      "string"
                        +    ],
                        +    "twenty": [
                        +      "string"
                        +    ],
                        +    "forty": [
                        +      "string"
                        +    ],
                        +    "oneHundred": [
                        +      "string"
                        +    ],
                        +    "unsure": [
                        +      "string"
                        +    ],
                        +    "end": "string",
                        +    "allowNonBoardMembers": true,
                        +    "estimation": 0
                        +  },
                        +  "targetId_gantt": [
                        +    "string"
                        +  ],
                        +  "linkType_gantt": [
                        +    0
                        +  ],
                        +  "linkId_gantt": [
                        +    "string"
                        +  ]
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -14394,7 +19320,7 @@ UserSecurity - + @@ -14407,15 +19333,22 @@ UserSecurity + + + + + + + - + - + @@ -14429,21 +19362,21 @@ UserSecurity - + - + - + @@ -14464,7 +19397,7 @@ UserSecurity - + @@ -14478,84 +19411,84 @@ UserSecurity - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -14569,14 +19502,14 @@ UserSecurity - - + + - + @@ -14590,14 +19523,49 @@ UserSecurity - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        titlestring|nullstring¦null false none the title of the cardis the card archived
                        archivedAtstring¦nullfalsenonelatest archiving date
                        parentIdstring|nullstring¦null false none ID of the parent card
                        listIdstring|nullstring¦null false none List ID where the card is
                        boardIdstring|nullstring¦null false none Board ID of the card
                        coverIdstring|nullstring¦null false none Cover ID of the card
                        colorstring|nullstring¦null false none none
                        customFields[CardsCustomfields]|null[CardsCustomfields]¦null false none list of custom fields
                        descriptionstring|nullstring¦null false none description of the card
                        requestedBystring|nullstring¦null false none who requested the card (ID of the user)
                        assignedBystring|nullstring¦null false none who assigned the card (ID of the user)
                        labelIds[string]|null[string]¦null false none list of labels ID the card has
                        members[string]|null[string]¦null false none list of members (user IDs)
                        assignees[string]|null[string]¦null false nonewho is assignee of the card (user ID), maximum one ID of assignee in array.who is assignee of the card (user ID),
                        maximum one ID of assignee in array.
                        receivedAtstring|nullstring¦null false none Date the card was received
                        startAtstring|nullstring¦null false none Date the card was started to be worked on
                        dueAtstring|nullstring¦null false none Date the card is due
                        endAtstring|nullstring¦null false none Date the card ended
                        spentTimenumber|nullnumber¦null false none How much time has been spent on this
                        isOvertimeboolean|nullboolean¦null false none is the card over time?
                        sortnumbertruenumber¦nullfalse none Sort value
                        subtaskSortnumber|nullnumber¦null false none subtask sort value
                        linkedIdstring|nullstring¦null false none ID of the linked card
                        voteCardsVotefalsenonenone
                        pokerCardsPokerfalsenonenone
                        targetId_gantt[string]¦nullfalsenoneID of card which is the child link in gantt view
                        linkType_gantt[number]¦nullfalsenoneID of card which is the parent link in gantt view
                        linkId_gantt[string]¦nullfalsenoneID of card which is the parent link in gantt view
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -14708,28 +19676,264 @@ UserSecurity
                        -

                        CardsCustomfields

                        -

                        -
                        {}
                        -
                        -
                        -

                        Properties

                        -

                        None

                        -

                        ChecklistItems

                        -

                        +

                        CardsVote

                        +

                        + + +

                        {
                        -  "title": "string",
                        -  "sort": 0,
                        -  "isFinished": true,
                        -  "checklistId": "string",
                        -  "cardId": "string",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string"
                        +  "question": "string",
                        +  "positive": [
                        +    "string"
                        +  ],
                        +  "negative": [
                        +    "string"
                        +  ],
                        +  "end": "string",
                        +  "public": true,
                        +  "allowNonBoardMembers": true
                         }
                         
                         
                        -

                        An item in a checklist

                        -

                        Properties

                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        questionstringtruenonenone
                        positive[string]falsenonelist of members (user IDs)
                        negative[string]falsenonelist of members (user IDs)
                        endstringfalsenonenone
                        publicbooleantruenonenone
                        allowNonBoardMembersbooleantruenonenone
                        +

                        CardsPoker

                        +

                        + + +

                        +
                        {
                        +  "question": true,
                        +  "one": [
                        +    "string"
                        +  ],
                        +  "two": [
                        +    "string"
                        +  ],
                        +  "three": [
                        +    "string"
                        +  ],
                        +  "five": [
                        +    "string"
                        +  ],
                        +  "eight": [
                        +    "string"
                        +  ],
                        +  "thirteen": [
                        +    "string"
                        +  ],
                        +  "twenty": [
                        +    "string"
                        +  ],
                        +  "forty": [
                        +    "string"
                        +  ],
                        +  "oneHundred": [
                        +    "string"
                        +  ],
                        +  "unsure": [
                        +    "string"
                        +  ],
                        +  "end": "string",
                        +  "allowNonBoardMembers": true,
                        +  "estimation": 0
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        questionbooleantruenonenone
                        one[string]falsenonepoker card one
                        two[string]falsenonepoker card two
                        three[string]falsenonepoker card three
                        five[string]falsenonepoker card five
                        eight[string]falsenonepoker card eight
                        thirteen[string]falsenonepoker card thirteen
                        twenty[string]falsenonepoker card twenty
                        forty[string]falsenonepoker card forty
                        oneHundred[string]falsenonepoker card oneHundred
                        unsure[string]falsenonepoker card unsure
                        endstringfalsenonenone
                        allowNonBoardMembersbooleantruenonenone
                        estimationnumberfalsenonepoker estimation value
                        +

                        CardsCustomfields

                        +

                        + + +

                        +
                        {}
                        +
                        +
                        +

                        Properties

                        +

                        None

                        +

                        ChecklistItems

                        +

                        + + +

                        +
                        {
                        +  "title": "string",
                        +  "sort": 0,
                        +  "isFinished": true,
                        +  "checklistId": "string",
                        +  "cardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string"
                        +}
                        +
                        +
                        +

                        An item in a checklist

                        +

                        Properties

                        @@ -14778,7 +19982,7 @@ UserSecurity - + @@ -14792,20 +19996,23 @@ UserSecurity
                        createdAtstring|nullstring¦null false none none
                        -

                        Checklists

                        -

                        +

                        Checklists

                        +

                        + + +

                        {
                        -  "cardId": "string",
                        -  "title": "string",
                        -  "finishedAt": "string",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "sort": 0
                        +  "cardId": "string",
                        +  "title": "string",
                        +  "finishedAt": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "sort": 0
                         }
                         
                         
                        -

                        A Checklist

                        -

                        Properties

                        +

                        A Checklist

                        +

                        Properties

                        @@ -14833,7 +20040,7 @@ UserSecurity - + @@ -14861,29 +20068,36 @@ UserSecurity
                        finishedAtstring|nullstring¦null false none When was the checklist finished
                        -

                        CustomFields

                        -

                        +

                        CustomFields

                        +

                        + + +

                        {
                        -  "boardIds": [
                        -    "string"
                        +  "boardIds": [
                        +    "string"
                           ],
                        -  "name": "string",
                        -  "type": "text",
                        -  "settings": {
                        -    "dropdownItems": [
                        +  "name": "string",
                        +  "type": "text",
                        +  "settings": {
                        +    "currencyCode": "string",
                        +    "dropdownItems": [
                               {}
                        -    ]
                        +    ],
                        +    "stringtemplateFormat": "string",
                        +    "stringtemplateSeparator": "string"
                           },
                        -  "showOnCard": true,
                        -  "automaticallyOnCard": true,
                        -  "showLabelOnMiniCard": true,
                        -  "createdAt": "string",
                        -  "modifiedAt": "string"
                        +  "showOnCard": true,
                        +  "automaticallyOnCard": true,
                        +  "alwaysOnCard": true,
                        +  "showLabelOnMiniCard": true,
                        +  "createdAt": "string",
                        +  "modifiedAt": "string"
                         }
                         
                         
                        -

                        A custom field on a card in the board

                        -

                        Properties

                        +

                        A custom field on a card in the board

                        +

                        Properties

                        @@ -14921,7 +20135,7 @@ UserSecurity - + @@ -14938,6 +20152,13 @@ UserSecurity + + + + + + + @@ -14946,7 +20167,7 @@ UserSecurity - + @@ -14960,7 +20181,7 @@ UserSecurity
                        CustomFieldsSettings true nonesettings of the custom fieldnone
                        showOnCardshould the custom fields automatically be added on cards?
                        alwaysOnCardbooleantruenoneshould the custom field be automatically added to all cards?
                        showLabelOnMiniCard boolean true
                        createdAtstring|nullstring¦null false none none
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -14985,18 +20206,36 @@ UserSecurity + + + + + + + + + + + +
                        type dropdown
                        typecheckbox
                        typecurrency
                        typestringtemplate
                        -

                        CustomFieldsSettings

                        -

                        +

                        CustomFieldsSettings

                        +

                        + + +

                        {
                        -  "dropdownItems": [
                        +  "currencyCode": "string",
                        +  "dropdownItems": [
                             {}
                        -  ]
                        +  ],
                        +  "stringtemplateFormat": "string",
                        +  "stringtemplateSeparator": "string"
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -15009,41 +20248,68 @@ UserSecurity + + + + + + + + + + + + + + + + + + + + +
                        currencyCodestringfalsenonenone
                        dropdownItems [CustomFieldsSettingsDropdownitems] false none list of drop down items objects
                        stringtemplateFormatstringfalsenonenone
                        stringtemplateSeparatorstringfalsenonenone
                        -

                        CustomFieldsSettingsDropdownitems

                        -

                        +

                        CustomFieldsSettingsDropdownitems

                        +

                        + + +

                        {}
                         
                         
                        -

                        Properties

                        +

                        Properties

                        None

                        -

                        Integrations

                        -

                        +

                        Integrations

                        +

                        + + +

                        {
                        -  "enabled": true,
                        -  "title": "string",
                        -  "type": "string",
                        -  "activities": [
                        -    "string"
                        +  "enabled": true,
                        +  "title": "string",
                        +  "type": "string",
                        +  "activities": [
                        +    "string"
                           ],
                        -  "url": "string",
                        -  "token": "string",
                        -  "boardId": "string",
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "userId": "string"
                        +  "url": "string",
                        +  "token": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "userId": "string"
                         }
                         
                         
                        -

                        Integration with third-party applications

                        -

                        Properties

                        +

                        Integration with third-party applications

                        +

                        Properties

                        @@ -15064,7 +20330,7 @@ UserSecurity - + @@ -15092,7 +20358,7 @@ UserSecurity - + @@ -15127,30 +20393,34 @@ UserSecurity
                        titlestring|nullstring¦null false none name of the integration
                        tokenstring|nullstring¦null false none token of the integration
                        -

                        Lists

                        -

                        +

                        Lists

                        +

                        + + +

                        {
                        -  "title": "string",
                        -  "starred": true,
                        -  "archived": true,
                        -  "boardId": "string",
                        -  "swimlaneId": "string",
                        -  "createdAt": "string",
                        -  "sort": 0,
                        -  "updatedAt": "string",
                        -  "modifiedAt": "string",
                        -  "wipLimit": {
                        -    "value": 0,
                        -    "enabled": true,
                        -    "soft": true
                        +  "title": "string",
                        +  "starred": true,
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "boardId": "string",
                        +  "swimlaneId": "string",
                        +  "createdAt": "string",
                        +  "sort": 0,
                        +  "updatedAt": "string",
                        +  "modifiedAt": "string",
                        +  "wipLimit": {
                        +    "value": 0,
                        +    "enabled": true,
                        +    "soft": true
                           },
                        -  "color": "white",
                        -  "type": "string"
                        +  "color": "white",
                        +  "type": "string"
                         }
                         
                         
                        -

                        A list (column) in the Wekan board.

                        -

                        Properties

                        +

                        A list (column) in the Wekan board.

                        +

                        Properties

                        @@ -15171,10 +20441,10 @@ UserSecurity - + - + @@ -15184,6 +20454,13 @@ UserSecurity + + + + + + + @@ -15206,14 +20483,14 @@ UserSecurity - + - + @@ -15230,11 +20507,11 @@ UserSecurity - + - + @@ -15248,7 +20525,7 @@ UserSecurity
                        starredboolean|nullboolean¦null false noneif a list is stared then we put it on the topif a list is stared
                        then we put it on the top
                        archivedis the list archived
                        archivedAtstring¦nullfalsenonelatest archiving date
                        boardId string true
                        sortnumber|nullnumber¦null false none is the list sorted
                        updatedAtstring|nullstring¦null false none last update of the listListsWiplimit false noneWIP object, see belownone
                        colorstring|nullstring¦null false none the color of the list
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -15303,6 +20580,10 @@ UserSecurity + + + + @@ -15355,16 +20636,19 @@ UserSecurity
                        colorsilver
                        color peachpuff
                        -

                        ListsWiplimit

                        -

                        +

                        ListsWiplimit

                        +

                        + + +

                        {
                        -  "value": 0,
                        -  "enabled": true,
                        -  "soft": true
                        +  "value": 0,
                        +  "enabled": true,
                        +  "soft": true
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -15399,23 +20683,27 @@ UserSecurity
                        -

                        Swimlanes

                        -

                        +

                        Swimlanes

                        +

                        + + +

                        {
                        -  "title": "string",
                        -  "archived": true,
                        -  "boardId": "string",
                        -  "createdAt": "string",
                        -  "sort": 0,
                        -  "color": "white",
                        -  "updatedAt": "string",
                        -  "modifiedAt": "string",
                        -  "type": "string"
                        +  "title": "string",
                        +  "archived": true,
                        +  "archivedAt": "string",
                        +  "boardId": "string",
                        +  "createdAt": "string",
                        +  "sort": 0,
                        +  "color": "white",
                        +  "updatedAt": "string",
                        +  "modifiedAt": "string",
                        +  "type": "string"
                         }
                         
                         
                        -

                        A swimlane is an line in the kaban board.

                        -

                        Properties

                        +

                        A swimlane is an line in the kaban board.

                        +

                        Properties

                        @@ -15442,6 +20730,13 @@ UserSecurity + + + + + + + @@ -15457,21 +20752,21 @@ UserSecurity - + - + - + @@ -15492,7 +20787,7 @@ UserSecurity
                        is the swimlane archived?
                        archivedAtstring¦nullfalsenonelatest archiving date of the swimlane
                        boardId string true
                        sortnumber|nullnumber¦null false none the sort value of the swimlane
                        colorstring|nullstring¦null false none the color of the swimlane
                        updatedAtstring|nullstring¦null false none when was the swimlane last edited
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -15547,6 +20842,10 @@ UserSecurity + + + + @@ -15599,58 +20898,85 @@ UserSecurity
                        colorsilver
                        color peachpuff
                        -

                        Users

                        -

                        +

                        Users

                        +

                        + + +

                        {
                        -  "username": "string",
                        -  "emails": [
                        +  "username": "string",
                        +  "orgs": [
                             {
                        -      "address": "string",
                        -      "verified": true
                        +      "orgId": "string",
                        +      "orgDisplayName": "string"
                             }
                           ],
                        -  "createdAt": "string",
                        -  "modifiedAt": "string",
                        -  "profile": {
                        -    "avatarUrl": "string",
                        -    "emailBuffer": [
                        -      "string"
                        +  "teams": [
                        +    {
                        +      "teamId": "string",
                        +      "teamDisplayName": "string"
                        +    }
                        +  ],
                        +  "emails": [
                        +    {
                        +      "address": "string",
                        +      "verified": true
                        +    }
                        +  ],
                        +  "createdAt": "string",
                        +  "modifiedAt": "string",
                        +  "profile": {
                        +    "avatarUrl": "string",
                        +    "emailBuffer": [
                        +      "string"
                             ],
                        -    "fullname": "string",
                        -    "showDesktopDragHandles": true,
                        -    "hiddenSystemMessages": true,
                        -    "hiddenMinicardLabelText": true,
                        -    "initials": "string",
                        -    "invitedBoards": [
                        -      "string"
                        +    "fullname": "string",
                        +    "showDesktopDragHandles": true,
                        +    "hideCheckedItems": true,
                        +    "cardMaximized": true,
                        +    "hiddenSystemMessages": true,
                        +    "hiddenMinicardLabelText": true,
                        +    "initials": "string",
                        +    "invitedBoards": [
                        +      "string"
                             ],
                        -    "language": "string",
                        -    "notifications": [],
                        -    "activity": "string",
                        -    "read": "string",
                        -    "showCardsCountAt": 0,
                        -    "starredBoards": [
                        -      "string"
                        +    "language": "string",
                        +    "notifications": [
                        +      {
                        +        "activity": "string",
                        +        "read": "string"
                        +      }
                             ],
                        -    "icode": "string",
                        -    "boardView": "board-view-lists",
                        -    "listSortBy": "-modifiedat",
                        -    "templatesBoardId": "string",
                        -    "cardTemplatesSwimlaneId": "string",
                        -    "listTemplatesSwimlaneId": "string",
                        -    "boardTemplatesSwimlaneId": "string"
                        +    "showCardsCountAt": 0,
                        +    "startDayOfWeek": 0,
                        +    "starredBoards": [
                        +      "string"
                        +    ],
                        +    "icode": "string",
                        +    "boardView": "board-view-swimlanes",
                        +    "listSortBy": "-modifiedat",
                        +    "templatesBoardId": "string",
                        +    "cardTemplatesSwimlaneId": "string",
                        +    "listTemplatesSwimlaneId": "string",
                        +    "boardTemplatesSwimlaneId": "string"
                           },
                        -  "services": {},
                        -  "heartbeat": "string",
                        -  "isAdmin": true,
                        -  "createdThroughApi": true,
                        -  "loginDisabled": true,
                        -  "authenticationMethod": "string"
                        +  "services": {},
                        +  "heartbeat": "string",
                        +  "isAdmin": true,
                        +  "createdThroughApi": true,
                        +  "loginDisabled": true,
                        +  "authenticationMethod": "string",
                        +  "sessionData": {
                        +    "totalHits": 0
                        +  },
                        +  "importUsernames": [
                        +    "string"
                        +  ]
                         }
                         
                         
                        -

                        A User in wekan

                        -

                        Properties

                        +

                        A User in wekan

                        +

                        Properties

                        @@ -15664,14 +20990,28 @@ UserSecurity - + + + + + + + + + + + + + + + - + @@ -15695,39 +21035,39 @@ UserSecurity - + - + - + - + - + - + @@ -15739,78 +21079,65 @@ UserSecurity - -
                        usernamestring|nullstring¦null false none the username of the user
                        orgs[UsersOrgs]¦nullfalsenonethe list of organizations that a user belongs to
                        teams[UsersTeams]¦nullfalsenonethe list of teams that a user belongs to
                        emails[UsersEmails]|null[UsersEmails]¦null false none the list of emails attached to a userUsersProfile false noneprofile settingsnone
                        servicesobject|nullobject¦null false none services field of the user
                        heartbeatstring|nullstring¦null false none last time the user has been seen
                        isAdminboolean|nullboolean¦null false none is the user an admin of the board?
                        createdThroughApiboolean|nullboolean¦null false none was the user created through the API?
                        loginDisabledboolean|nullboolean¦null false none loginDisabled field of the usernone authentication method of the user
                        -

                        UsersEmails

                        -

                        -
                        {
                        -  "address": "string",
                        -  "verified": true
                        -}
                        -
                        -
                        -

                        Properties

                        - - - - - - - - - - - - - - + + + + - - - - + + + - +
                        NameTypeRequiredRestrictionsDescription
                        addressstringtruesessionDataUsersSessiondatafalsenone noneThe email address
                        verifiedbooleantrueimportUsernames[string]¦nullfalse noneHas the email been verifiedusername for imported
                        -

                        UsersProfile

                        -

                        +

                        UsersProfile

                        +

                        + + +

                        {
                        -  "avatarUrl": "string",
                        -  "emailBuffer": [
                        -    "string"
                        +  "avatarUrl": "string",
                        +  "emailBuffer": [
                        +    "string"
                           ],
                        -  "fullname": "string",
                        -  "showDesktopDragHandles": true,
                        -  "hiddenSystemMessages": true,
                        -  "hiddenMinicardLabelText": true,
                        -  "initials": "string",
                        -  "invitedBoards": [
                        -    "string"
                        +  "fullname": "string",
                        +  "showDesktopDragHandles": true,
                        +  "hideCheckedItems": true,
                        +  "cardMaximized": true,
                        +  "hiddenSystemMessages": true,
                        +  "hiddenMinicardLabelText": true,
                        +  "initials": "string",
                        +  "invitedBoards": [
                        +    "string"
                           ],
                        -  "language": "string",
                        -  "notifications": [],
                        -  "activity": "string",
                        -  "read": "string",
                        -  "showCardsCountAt": 0,
                        -  "starredBoards": [
                        -    "string"
                        +  "language": "string",
                        +  "notifications": [
                        +    {
                        +      "activity": "string",
                        +      "read": "string"
                        +    }
                           ],
                        -  "icode": "string",
                        -  "boardView": "board-view-lists",
                        -  "listSortBy": "-modifiedat",
                        -  "templatesBoardId": "string",
                        -  "cardTemplatesSwimlaneId": "string",
                        -  "listTemplatesSwimlaneId": "string",
                        -  "boardTemplatesSwimlaneId": "string"
                        +  "showCardsCountAt": 0,
                        +  "startDayOfWeek": 0,
                        +  "starredBoards": [
                        +    "string"
                        +  ],
                        +  "icode": "string",
                        +  "boardView": "board-view-swimlanes",
                        +  "listSortBy": "-modifiedat",
                        +  "templatesBoardId": "string",
                        +  "cardTemplatesSwimlaneId": "string",
                        +  "listTemplatesSwimlaneId": "string",
                        +  "boardTemplatesSwimlaneId": "string"
                         }
                         
                         
                        -

                        Properties

                        +

                        Properties

                        @@ -15848,7 +21175,21 @@ UserSecurity - + + + + + + + + + + + + + + + @@ -15893,20 +21234,6 @@ UserSecurity - - - - - - - - - - - - - - @@ -15914,6 +21241,13 @@ UserSecurity + + + + + + + @@ -15971,7 +21305,7 @@ UserSecurity
                        boolean false nonedoes the user want to hide system messages?does the user want to show desktop drag handles?
                        hideCheckedItemsbooleanfalsenonedoes the user want to hide checked checklist items?
                        cardMaximizedbooleanfalsenonehas user clicked maximize card?
                        hiddenSystemMessagesenabled notifications for the user
                        activitystringtruenoneThe id of the activity this notification references
                        readstringfalsenonethe date on which this notification was read
                        showCardsCountAt number falseshowCardCountAt field of the user
                        startDayOfWeeknumberfalsenonestartDayOfWeek field of the user
                        starredBoards [string] false
                        -

                        Enumerated Values

                        +

                        Enumerated Values

                        @@ -15982,11 +21316,11 @@ UserSecurity - + - + @@ -16018,7 +21352,221 @@ UserSecurity
                        boardViewboard-view-listsboard-view-swimlanes
                        boardViewboard-view-swimlanesboard-view-lists
                        boardView
                        +

                        UsersSessiondata

                        +

                        + + +

                        +
                        {
                        +  "totalHits": 0
                        +}
                         
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        totalHitsnumberfalsenoneTotal hits from last searchquery['members.userId'] = Meteor.userId();
                        last hit that was returned
                        +

                        UsersOrgs

                        +

                        + + +

                        +
                        {
                        +  "orgId": "string",
                        +  "orgDisplayName": "string"
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        orgIdstringtruenoneThe uniq ID of the organization
                        orgDisplayNamestringtruenoneThe display name of the organization
                        +

                        UsersTeams

                        +

                        + + +

                        +
                        {
                        +  "teamId": "string",
                        +  "teamDisplayName": "string"
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        teamIdstringtruenoneThe uniq ID of the team
                        teamDisplayNamestringtruenoneThe display name of the team
                        +

                        UsersEmails

                        +

                        + + +

                        +
                        {
                        +  "address": "string",
                        +  "verified": true
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        addressstringtruenoneThe email address
                        verifiedbooleantruenoneHas the email been verified
                        +

                        UsersProfileNotifications

                        +

                        + + +

                        +
                        {
                        +  "activity": "string",
                        +  "read": "string"
                        +}
                        +
                        +
                        +

                        Properties

                        + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        NameTypeRequiredRestrictionsDescription
                        activitystringtruenoneThe id of the activity this notification references
                        readstringfalsenonethe date on which this notification was read
                        + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                        @@ -16056,6 +21604,10 @@ UserSecurity Go + + PHP + +
                        diff --git a/public/api/wekan.yml b/public/api/wekan.yml index 003c260f6..841885259 100644 --- a/public/api/wekan.yml +++ b/public/api/wekan.yml @@ -1,7 +1,7 @@ swagger: '2.0' info: title: Wekan REST API - version: v3.90 + version: v5.38 description: | The REST API allows you to control and extend Wekan with ease. @@ -288,20 +288,17 @@ paths: '200': description: |- 200 response - /api/boards/{board}/cards/{card}/checklists: + /api/boards/{board}/attachments: get: - operationId: get_board_card_checklists + operationId: get_board_attachments + summary: Get the list of attachments of a board tags: - - Checklists + - Boards parameters: - name: board in: path - description: the board value - type: string - required: true - - name: card - in: path - description: the card value + description: | + the board ID type: string required: true produces: @@ -312,8 +309,92 @@ paths: '200': description: |- 200 response + schema: + type: array + items: + type: object + properties: + attachmentId: + type: string + attachmentName: + type: string + attachmentType: + type: string + cardId: + type: string + listId: + type: string + swimlaneId: + type: string + /api/boards/{board}/attachments/{attachment}/export: + get: + operationId: exportJson + summary: This route is used to export a attachement to a json file format. + description: | + If user is already logged-in, pass loginToken as param + "authToken": '/api/boards/:boardId/attachments/:attachmentId/export?authToken=:token' + tags: + - Boards + parameters: + - name: board + in: path + description: | + the ID of the board we are exporting + type: string + required: true + - name: attachment + in: path + description: | + the ID of the attachment we are exporting + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + /api/boards/{board}/cards/{card}/checklists: + get: + operationId: get_all_checklists + summary: Get the list of checklists attached to a card + tags: + - Checklists + parameters: + - name: board + in: path + description: | + the board ID + type: string + required: true + - name: card + in: path + description: | + the card ID + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: array + items: + type: object + properties: + _id: + type: string + title: + type: string post: - operationId: post_board_card_checklists + operationId: new_checklist + summary: create a new checklist tags: - Checklists consumes: @@ -322,22 +403,26 @@ paths: parameters: - name: title in: formData - description: the title value + description: | + the title of the new checklist type: string required: true - name: items in: formData - description: the items value + description: | + the list of items on the new checklist type: string - required: true + required: false - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true produces: @@ -348,25 +433,34 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/cards/{card}/checklists/{checklist}: get: - operationId: get_board_card_checklist + operationId: get_checklist + summary: Get a checklist tags: - Checklists parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true - name: checklist in: path - description: the checklist value + description: | + the ID of the checklist type: string required: true produces: @@ -377,24 +471,54 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + cardId: + type: string + title: + type: string + finishedAt: + type: string + createdAt: + type: string + sort: + type: number + items: + type: array + items: + type: object + properties: + _id: + type: string + title: + type: string + isFinished: + type: boolean delete: - operationId: delete_board_card_checklist + operationId: delete_checklist + summary: Delete a checklist + description: | + The checklist will be removed, not put in the recycle bin. tags: - Checklists parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true - name: checklist in: path - description: the checklist value + description: | + the ID of the checklist to remove type: string required: true produces: @@ -405,30 +529,41 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/cards/{card}/checklists/{checklist}/items/{item}: get: - operationId: get_board_card_checklist_item + operationId: get_checklist_item + summary: Get a checklist item tags: - ChecklistItems + - Checklists parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true - name: checklist in: path - description: the checklist value + description: | + the checklist ID type: string required: true - name: item in: path - description: the item value + description: | + the ID of the item type: string required: true produces: @@ -439,42 +574,52 @@ paths: '200': description: |- 200 response + schema: + $ref: "#/definitions/ChecklistItems" put: - operationId: put_board_card_checklist_item + operationId: edit_checklist_item + summary: Edit a checklist item tags: - ChecklistItems + - Checklists consumes: - multipart/form-data - application/json parameters: - name: isFinished in: formData - description: the isFinished value + description: | + is the item checked? type: string - required: true + required: false - name: title in: formData - description: the title value + description: | + the new text of the item type: string - required: true + required: false - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true - name: checklist in: path - description: the checklist value + description: | + the checklist ID type: string required: true - name: item in: path - description: the item value + description: | + the ID of the item type: string required: true produces: @@ -485,29 +630,42 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string delete: - operationId: delete_board_card_checklist_item + operationId: delete_checklist_item + summary: Delete a checklist item + description: | + Note: this operation can't be reverted. tags: - ChecklistItems + - Checklists parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true - name: checklist in: path - description: the checklist value + description: | + the checklist ID type: string required: true - name: item in: path - description: the item value + description: | + the ID of the item to be removed type: string required: true produces: @@ -518,6 +676,11 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/cards/{card}/comments: get: operationId: get_all_comments @@ -557,7 +720,8 @@ paths: authorId: type: string post: - operationId: post_board_card_comments + operationId: new_comment + summary: Add a comment on a card tags: - CardComments consumes: @@ -566,7 +730,8 @@ paths: parameters: - name: authorId in: formData - description: the authorId value + description: | + the user who 'posted' the comment type: string required: true - name: comment @@ -576,12 +741,14 @@ paths: required: true - name: board in: path - description: the board value + description: | + the board ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the ID of the card type: string required: true produces: @@ -592,25 +759,34 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/cards/{card}/comments/{comment}: get: - operationId: get_board_card_comment + operationId: get_comment + summary: Get a comment on a card tags: - CardComments parameters: - name: board in: path - description: the board value + description: | + the board ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the ID of the card type: string required: true - name: comment in: path - description: the comment value + description: | + the ID of the comment to retrieve type: string required: true produces: @@ -621,24 +797,30 @@ paths: '200': description: |- 200 response + schema: + $ref: "#/definitions/CardComments" delete: - operationId: delete_board_card_comment + operationId: delete_comment + summary: Delete a comment on a card tags: - CardComments parameters: - name: board in: path - description: the board value + description: | + the board ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the ID of the card type: string required: true - name: comment in: path - description: the comment value + description: | + the ID of the comment to delete type: string required: true produces: @@ -649,6 +831,59 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string + /api/boards/{board}/cardsByCustomField/{customField}/{customFieldValue}: + get: + operationId: get_cards_by_custom_field + summary: Get all Cards that matchs a value of a specific custom field + tags: + - Cards + parameters: + - name: board + in: path + description: | + the board ID + type: string + required: true + - name: customField + in: path + description: | + the list ID + type: string + required: true + - name: customFieldValue + in: path + description: | + the value to look for + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: array + items: + type: object + properties: + _id: + type: string + title: + type: string + description: + type: string + listId: + type: string + swinlaneId: + type: string /api/boards/{board}/custom-fields: get: operationId: get_all_custom_fields @@ -750,10 +985,89 @@ paths: type: string /api/boards/{board}/custom-fields/{customField}: get: - operationId: get_board_customField + operationId: get_custom_field + summary: Get a Custom Fields attached to a board tags: - CustomFields parameters: + - name: board + in: path + description: the board value + type: string + required: true + - name: customField + in: path + description: | + the ID of the custom field + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: array + items: + type: object + properties: + _id: + type: string + boardIds: + type: string + put: + operationId: edit_custom_field + summary: Update a Custom Field + tags: + - CustomFields + consumes: + - multipart/form-data + - application/json + parameters: + - name: name + in: formData + description: | + the name of the custom field + type: string + required: true + - name: type + in: formData + description: | + the type of the custom field + type: string + required: true + - name: settings + in: formData + description: | + the settings object of the custom field + type: string + required: true + - name: showOnCard + in: formData + description: | + should we show the custom field on cards + type: boolean + required: true + - name: automaticallyOnCard + in: formData + description: | + should the custom fields automatically be added on cards + type: boolean + required: true + - name: alwaysOnCard + in: formData + description: the alwaysOnCard value + type: string + required: true + - name: showLabelOnMiniCard + in: formData + description: | + should the label of the custom field be shown on minicards + type: boolean + required: true - name: board in: path description: the board value @@ -772,11 +1086,59 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string delete: - operationId: delete_board_customField + operationId: delete_custom_field + summary: Delete a Custom Fields attached to a board + description: | + The Custom Field can't be retrieved after this operation tags: - CustomFields parameters: + - name: board + in: path + description: the board value + type: string + required: true + - name: customField + in: path + description: | + the ID of the custom field + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: object + properties: + _id: + type: string + /api/boards/{board}/custom-fields/{customField}/dropdown-items: + post: + operationId: add_custom_field_dropdown_items + summary: Update a Custom Field's dropdown items + tags: + - CustomFields + consumes: + - multipart/form-data + - application/json + parameters: + - name: items + in: formData + description: | + names of the custom field + type: string + required: false - name: board in: path description: the board value @@ -795,10 +1157,93 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string + /api/boards/{board}/custom-fields/{customField}/dropdown-items/{dropdownItem}: + put: + operationId: edit_custom_field_dropdown_item + summary: Update a Custom Field's dropdown item + tags: + - CustomFields + consumes: + - multipart/form-data + - application/json + parameters: + - name: name + in: formData + description: | + names of the custom field + type: string + required: true + - name: board + in: path + description: the board value + type: string + required: true + - name: customField + in: path + description: the customField value + type: string + required: true + - name: dropdownItem + in: path + description: the dropdownItem value + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: object + properties: + _id: + type: string + delete: + operationId: delete_custom_field_dropdown_item + summary: Update a Custom Field's dropdown items + tags: + - CustomFields + parameters: + - name: board + in: path + description: the board value + type: string + required: true + - name: customField + in: path + description: the customField value + type: string + required: true + - name: dropdownItem + in: path + description: the dropdownItem value + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/export: get: - operationId: export - summary: This route is used to export the board. + operationId: exportJson + summary: This route is used to export the board to a json file format. description: | If user is already logged-in, pass loginToken as param "authToken": '/api/boards/:boardId/export?authToken=:token' @@ -1007,18 +1452,21 @@ paths: type: string /api/boards/{board}/integrations/{int}/activities: delete: - operationId: delete_board_int_activities + operationId: delete_integration_activities + summary: Delete subscribed activities tags: - Integrations parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: int in: path - description: the int value + description: | + the integration ID type: string required: true produces: @@ -1029,8 +1477,11 @@ paths: '200': description: |- 200 response + schema: + $ref: "#/definitions/Integrations" post: - operationId: post_board_int_activities + operationId: new_integration_activities + summary: Add subscribed activities tags: - Integrations consumes: @@ -1044,12 +1495,14 @@ paths: required: true - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: int in: path - description: the int value + description: | + the integration ID type: string required: true produces: @@ -1060,6 +1513,8 @@ paths: '200': description: |- 200 response + schema: + $ref: "#/definitions/Integrations" /api/boards/{board}/labels: put: operationId: add_board_label @@ -1330,23 +1785,27 @@ paths: type: string /api/boards/{board}/lists/{list}/cards/{card}: get: - operationId: get_board_list_card + operationId: get_card + summary: Get a Card tags: - Cards parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: list in: path - description: the list value + description: | + the list ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the card ID type: string required: true produces: @@ -1357,8 +1816,23 @@ paths: '200': description: |- 200 response + schema: + $ref: "#/definitions/Cards" put: - operationId: put_board_list_card + operationId: edit_card + summary: Edit Fields in a Card + description: | + Edit a card + + The color has to be chosen between `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`: + + Wekan card colors + + Note: setting the color to white has the same effect than removing it. tags: - Cards consumes: @@ -1367,112 +1841,152 @@ paths: parameters: - name: title in: formData - description: the title value + description: | + the new title of the card type: string - required: true - - name: listId + required: false + - name: sort in: formData - description: the listId value + description: | + the new sort value of the card type: string - required: true - - name: authorId - in: formData - description: the authorId value - type: string - required: true + required: false - name: parentId in: formData - description: the parentId value + description: | + change the parent of the card type: string - required: true + required: false - name: description in: formData - description: the description value + description: | + the new description of the card type: string - required: true + required: false - name: color in: formData - description: the color value + description: | + the new color of the card type: string - required: true + required: false + - name: vote + in: formData + description: | + the vote object + type: object + required: false + - name: poker + in: formData + description: | + the poker object + type: object + required: false - name: labelIds in: formData - description: the labelIds value + description: | + the new list of label IDs attached to the card type: string - required: true + required: false - name: requestedBy in: formData - description: the requestedBy value + description: | + the new requestedBy field of the card type: string - required: true + required: false - name: assignedBy in: formData - description: the assignedBy value + description: | + the new assignedBy field of the card type: string - required: true + required: false - name: receivedAt in: formData - description: the receivedAt value + description: | + the new receivedAt field of the card type: string - required: true + required: false - name: startAt in: formData - description: the startAt value + description: | + the new startAt field of the card type: string - required: true + required: false - name: dueAt in: formData - description: the dueAt value + description: | + the new dueAt field of the card type: string - required: true + required: false - name: endAt in: formData - description: the endAt value + description: | + the new endAt field of the card type: string - required: true + required: false - name: spentTime in: formData - description: the spentTime value + description: | + the new spentTime field of the card type: string - required: true + required: false - name: isOverTime in: formData - description: the isOverTime value - type: string - required: true + description: | + the new isOverTime field of the card + type: boolean + required: false - name: customFields in: formData - description: the customFields value + description: | + the new customFields value of the card type: string - required: true + required: false - name: members in: formData - description: the members value + description: | + the new list of member IDs attached to the card type: string - required: true + required: false - name: assignees in: formData - description: the assignees value + description: | + the array of maximum one ID of assignee attached to the card type: string - required: true + required: false - name: swimlaneId in: formData - description: the swimlaneId value + description: | + the new swimlane ID of the card type: string - required: true + required: false + - name: listId + in: formData + description: | + the new list ID of the card (move operation) + type: string + required: false + - name: authorId + in: formData + description: | + change the owner of the card + type: string + required: false - name: board in: path - description: the board value + description: | + the board ID of the card type: string required: true - name: list in: path - description: the list value + description: | + the list ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the ID of the card type: string required: true produces: @@ -1483,24 +1997,36 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string delete: - operationId: delete_board_list_card + operationId: delete_card + summary: Delete a card from a board + description: | + This operation **deletes** a card, and therefore the card + is not put in the recycle bin. tags: - Cards parameters: - name: board in: path - description: the board value + description: | + the board ID of the card type: string required: true - name: list in: path - description: the list value + description: | + the list ID of the card type: string required: true - name: card in: path - description: the card value + description: | + the ID of the card type: string required: true produces: @@ -1511,6 +2037,11 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/members/{member}: post: operationId: set_board_member_permission @@ -1634,26 +2165,33 @@ paths: type: string /api/boards/{board}/members/{user}/remove: post: - operationId: post_board_user_remove + operationId: remove_board_member + summary: Remove Member from Board + description: | + Only the admin user (the first user) can call the REST API. tags: - Users + - Boards consumes: - multipart/form-data - application/json parameters: - name: action in: formData - description: the action value + description: | + the action (needs to be `remove`) type: string required: true - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: user in: path - description: the user value + description: | + the user ID type: string required: true produces: @@ -1664,6 +2202,13 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string + title: + type: string /api/boards/{board}/swimlanes: get: operationId: get_all_swimlanes @@ -1758,18 +2303,23 @@ paths: schema: $ref: "#/definitions/Swimlanes" delete: - operationId: delete_board_swimlane + operationId: delete_swimlane + summary: Delete a swimlane + description: | + The swimlane will be deleted, not moved to the recycle bin tags: - Swimlanes parameters: - name: board in: path - description: the board value + description: | + the ID of the board type: string required: true - name: swimlane in: path - description: the swimlane value + description: | + the ID of the swimlane type: string required: true produces: @@ -1780,20 +2330,28 @@ paths: '200': description: |- 200 response + schema: + type: object + properties: + _id: + type: string /api/boards/{board}/swimlanes/{swimlane}/cards: get: - operationId: get_board_swimlane_cards + operationId: get_swimlane_cards + summary: get all cards attached to a swimlane tags: - Cards parameters: - name: board in: path - description: the board value + description: | + the board ID type: string required: true - name: swimlane in: path - description: the swimlane value + description: | + the swimlane ID type: string required: true produces: @@ -1804,6 +2362,71 @@ paths: '200': description: |- 200 response + schema: + type: array + items: + type: object + properties: + _id: + type: string + title: + type: string + description: + type: string + listId: + type: string + /api/boards_count: + get: + operationId: get_boards_count + summary: Get public and private boards count + tags: + - Boards + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: object + properties: + private: + type: integer + public: + type: integer + /api/createtoken/{user}: + post: + operationId: create_user_token + summary: Create a user token + description: | + Only the admin user (the first user) can call the REST API. + tags: + - Users + consumes: + - multipart/form-data + - application/json + parameters: + - name: user + in: path + description: | + the ID of the user to create token for. + type: string + required: true + produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response + schema: + type: object + properties: + _id: + type: string /api/user: get: operationId: get_current_user @@ -1899,7 +2522,7 @@ paths: - name: user in: path description: | - the user ID + the user ID or username type: string required: true produces: @@ -2029,6 +2652,11 @@ definitions: description: | Is the board archived? type: boolean + archivedAt: + description: | + Latest archiving time of the board + type: string + x-nullable: true createdAt: description: | Creation time of the board @@ -2048,6 +2676,7 @@ definitions: type: array items: $ref: "#/definitions/BoardsLabels" + x-nullable: true members: description: | List of members of a board @@ -2061,6 +2690,20 @@ definitions: enum: - public - private + orgs: + description: | + the list of organizations that a board belongs to + type: array + items: + $ref: "#/definitions/BoardsOrgs" + x-nullable: true + teams: + description: | + the list of teams that a board belongs to + type: array + items: + $ref: "#/definitions/BoardsTeams" + x-nullable: true color: description: | The color of the board. @@ -2078,6 +2721,10 @@ definitions: - dark - relax - corteza + - clearblue + - natural + - modern + - moderndark description: description: | The description of the board @@ -2131,6 +2778,10 @@ definitions: description: | Does the board allows labels? type: boolean + allowsCreator: + description: | + Does the board allow creator? + type: boolean allowsAssignee: description: | Does the board allows assignee? @@ -2143,6 +2794,10 @@ definitions: description: | Does the board allows requested by? type: boolean + allowsCardSortingByNumber: + description: | + Does the board allows card sorting by number? + type: boolean allowsAssignedBy: description: | Does the board allows requested by? @@ -2208,14 +2863,22 @@ definitions: type: description: | The type of board + possible values: board, template-board, template-container type: string + enum: + - board + - template-board + - template-container + sort: + description: | + Sort value + type: number required: - title - slug - archived - createdAt - stars - - labels - members - permission - color @@ -2227,15 +2890,18 @@ definitions: - allowsDescriptionText - allowsActivities - allowsLabels + - allowsCreator - allowsAssignee - allowsMembers - allowsRequestedBy + - allowsCardSortingByNumber - allowsAssignedBy - allowsReceivedDate - allowsStartDate - allowsEndDate - allowsDueDate - type + - sort BoardsLabels: type: object properties: @@ -2258,6 +2924,7 @@ definitions: `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo` type: string enum: + - white - green - yellow - orange @@ -2316,6 +2983,44 @@ definitions: - userId - isAdmin - isActive + BoardsOrgs: + type: object + properties: + orgId: + description: | + The uniq ID of the organization + type: string + orgDisplayName: + description: | + The display name of the organization + type: string + isActive: + description: | + Is the organization active? + type: boolean + required: + - orgId + - orgDisplayName + - isActive + BoardsTeams: + type: object + properties: + teamId: + description: | + The uniq ID of the team + type: string + teamDisplayName: + description: | + The display name of the team + type: string + isActive: + description: | + Is the team active? + type: boolean + required: + - teamId + - teamDisplayName + - isActive CardComments: type: object description: A comment on a card @@ -2361,6 +3066,11 @@ definitions: description: | is the card archived type: boolean + archivedAt: + description: | + latest archiving date + type: string + x-nullable: true parentId: description: | ID of the parent card @@ -2509,6 +3219,7 @@ definitions: description: | Sort value type: number + x-nullable: true subtaskSort: description: | subtask sort value @@ -2523,6 +3234,40 @@ definitions: ID of the linked card type: string x-nullable: true + vote: + description: | + vote object, see below + $ref: "#/definitions/CardsVote" + x-nullable: true + poker: + description: | + poker object, see below + $ref: "#/definitions/CardsPoker" + x-nullable: true + targetId_gantt: + description: | + ID of card which is the child link in gantt view + type: array + items: + type: string + x-nullable: true + x-nullable: true + linkType_gantt: + description: | + ID of card which is the parent link in gantt view + type: array + items: + type: number + x-nullable: true + x-nullable: true + linkId_gantt: + description: | + ID of card which is the parent link in gantt view + type: array + items: + type: string + x-nullable: true + x-nullable: true required: - archived - swimlaneId @@ -2530,8 +3275,122 @@ definitions: - modifiedAt - dateLastActivity - userId - - sort - type + CardsVote: + type: object + properties: + question: + type: string + positive: + description: | + list of members (user IDs) + type: array + items: + type: string + x-nullable: true + negative: + description: | + list of members (user IDs) + type: array + items: + type: string + x-nullable: true + end: + type: string + public: + type: boolean + allowNonBoardMembers: + type: boolean + required: + - question + - public + - allowNonBoardMembers + CardsPoker: + type: object + properties: + question: + type: boolean + one: + description: | + poker card one + type: array + items: + type: string + x-nullable: true + two: + description: | + poker card two + type: array + items: + type: string + x-nullable: true + three: + description: | + poker card three + type: array + items: + type: string + x-nullable: true + five: + description: | + poker card five + type: array + items: + type: string + x-nullable: true + eight: + description: | + poker card eight + type: array + items: + type: string + x-nullable: true + thirteen: + description: | + poker card thirteen + type: array + items: + type: string + x-nullable: true + twenty: + description: | + poker card twenty + type: array + items: + type: string + x-nullable: true + forty: + description: | + poker card forty + type: array + items: + type: string + x-nullable: true + oneHundred: + description: | + poker card oneHundred + type: array + items: + type: string + x-nullable: true + unsure: + description: | + poker card unsure + type: array + items: + type: string + x-nullable: true + end: + type: string + allowNonBoardMembers: + type: boolean + estimation: + description: | + poker estimation value + type: number + required: + - question + - allowNonBoardMembers CardsCustomfields: type: object ChecklistItems: @@ -2626,6 +3485,9 @@ definitions: - number - date - dropdown + - checkbox + - currency + - stringtemplate settings: description: | settings of the custom field @@ -2638,6 +3500,10 @@ definitions: description: | should the custom fields automatically be added on cards? type: boolean + alwaysOnCard: + description: | + should the custom field be automatically added to all cards? + type: boolean showLabelOnMiniCard: description: | should the label of the custom field be shown on minicards? @@ -2654,17 +3520,24 @@ definitions: - settings - showOnCard - automaticallyOnCard + - alwaysOnCard - showLabelOnMiniCard - modifiedAt CustomFieldsSettings: type: object properties: + currencyCode: + type: string dropdownItems: description: | list of drop down items objects type: array items: $ref: "#/definitions/CustomFieldsSettingsDropdownitems" + stringtemplateFormat: + type: string + stringtemplateSeparator: + type: string CustomFieldsSettingsDropdownitems: type: object Integrations: @@ -2738,6 +3611,11 @@ definitions: description: | is the list archived type: boolean + archivedAt: + description: | + latest archiving date + type: string + x-nullable: true boardId: description: | the board associated to this list @@ -2783,6 +3661,7 @@ definitions: - lime - pink - black + - silver - peachpuff - crimson - plum @@ -2840,6 +3719,11 @@ definitions: description: | is the swimlane archived? type: boolean + archivedAt: + description: | + latest archiving date of the swimlane + type: string + x-nullable: true boardId: description: | the ID of the board the swimlane is attached to @@ -2869,6 +3753,7 @@ definitions: - lime - pink - black + - silver - peachpuff - crimson - plum @@ -2910,6 +3795,20 @@ definitions: the username of the user type: string x-nullable: true + orgs: + description: | + the list of organizations that a user belongs to + type: array + items: + $ref: "#/definitions/UsersOrgs" + x-nullable: true + teams: + description: | + the list of teams that a user belongs to + type: array + items: + $ref: "#/definitions/UsersTeams" + x-nullable: true emails: description: | the list of emails attached to a user @@ -2957,24 +3856,23 @@ definitions: description: | authentication method of the user type: string + sessionData: + description: | + profile settings + $ref: "#/definitions/UsersSessiondata" + x-nullable: true + importUsernames: + description: | + username for imported + type: array + items: + type: string + x-nullable: true + x-nullable: true required: - createdAt - modifiedAt - authenticationMethod - UsersEmails: - type: object - properties: - address: - description: | - The email address - type: string - verified: - description: | - Has the email been verified - type: boolean - required: - - address - - verified UsersProfile: type: object properties: @@ -2995,7 +3893,15 @@ definitions: type: string showDesktopDragHandles: description: | - does the user want to hide system messages? + does the user want to show desktop drag handles? + type: boolean + hideCheckedItems: + description: | + does the user want to hide checked checklist items? + type: boolean + cardMaximized: + description: | + has user clicked maximize card? type: boolean hiddenSystemMessages: description: | @@ -3026,18 +3932,14 @@ definitions: type: array items: $ref: "#/definitions/UsersProfileNotifications" - activity: - description: | - The id of the activity this notification references - type: string - read: - description: | - the date on which this notification was read - type: string showCardsCountAt: description: | showCardCountAt field of the user type: number + startDayOfWeek: + description: | + startDayOfWeek field of the user + type: number starredBoards: description: | list of starred board IDs @@ -3054,8 +3956,8 @@ definitions: boardView field of the user type: string enum: - - board-view-lists - board-view-swimlanes + - board-view-lists - board-view-cal listSortBy: description: | @@ -3085,8 +3987,70 @@ definitions: Reference to the board templates swimlane Id type: string required: - - activity - templatesBoardId - cardTemplatesSwimlaneId - listTemplatesSwimlaneId - boardTemplatesSwimlaneId + UsersSessiondata: + type: object + properties: + totalHits: + description: | + Total hits from last searchquery['members.userId'] = Meteor.userId(); + last hit that was returned + type: number + UsersOrgs: + type: object + properties: + orgId: + description: | + The uniq ID of the organization + type: string + orgDisplayName: + description: | + The display name of the organization + type: string + required: + - orgId + - orgDisplayName + UsersTeams: + type: object + properties: + teamId: + description: | + The uniq ID of the team + type: string + teamDisplayName: + description: | + The display name of the team + type: string + required: + - teamId + - teamDisplayName + UsersEmails: + type: object + properties: + address: + description: | + The email address + type: string + verified: + description: | + Has the email been verified + type: boolean + required: + - address + - verified + UsersProfileNotifications: + type: object + properties: + activity: + description: | + The id of the activity this notification references + type: string + read: + description: | + the date on which this notification was read + type: string + required: + - activity diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 000000000..bcd461151 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 000000000..a47e5a5b8 --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #2d89ef + + + diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 000000000..57eee7644 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 000000000..8fb6ae020 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 000000000..df376031c Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/fonts/poppins-bold.woff b/public/fonts/poppins-bold.woff new file mode 100644 index 000000000..cc7e84477 Binary files /dev/null and b/public/fonts/poppins-bold.woff differ diff --git a/public/fonts/poppins-medium.woff b/public/fonts/poppins-medium.woff new file mode 100644 index 000000000..b8ccafebe Binary files /dev/null and b/public/fonts/poppins-medium.woff differ diff --git a/public/fonts/poppins-regular.woff b/public/fonts/poppins-regular.woff new file mode 100644 index 000000000..1ff396050 Binary files /dev/null and b/public/fonts/poppins-regular.woff differ diff --git a/public/maskable_icon.png b/public/maskable_icon.png new file mode 100644 index 000000000..cc8818a43 Binary files /dev/null and b/public/maskable_icon.png differ diff --git a/public/monochrome-icon-512x512.png b/public/monochrome-icon-512x512.png new file mode 100644 index 000000000..feba2ac7d Binary files /dev/null and b/public/monochrome-icon-512x512.png differ diff --git a/public/mstile-144x144.png b/public/mstile-144x144.png new file mode 100644 index 000000000..dbc184f45 Binary files /dev/null and b/public/mstile-144x144.png differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png new file mode 100644 index 000000000..943e09722 Binary files /dev/null and b/public/mstile-150x150.png differ diff --git a/public/mstile-310x150.png b/public/mstile-310x150.png new file mode 100644 index 000000000..f80bea7af Binary files /dev/null and b/public/mstile-310x150.png differ diff --git a/public/mstile-310x310.png b/public/mstile-310x310.png new file mode 100644 index 000000000..4d7ac6517 Binary files /dev/null and b/public/mstile-310x310.png differ diff --git a/public/mstile-70x70.png b/public/mstile-70x70.png new file mode 100644 index 000000000..8b306ba5c Binary files /dev/null and b/public/mstile-70x70.png differ diff --git a/public/pwa-service-worker.js b/public/pwa-service-worker.js new file mode 100644 index 000000000..3cc74ec95 --- /dev/null +++ b/public/pwa-service-worker.js @@ -0,0 +1,4 @@ +self.addEventListener('install', function(event) { + // Dummy service worker that does nothing, + // for mobile browsers "Add to home screen". +}); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 000000000..1f53798bb --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 000000000..4320882c9 --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,51 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/public/screenshot1.webp b/public/screenshot1.webp new file mode 100644 index 000000000..c023b5774 Binary files /dev/null and b/public/screenshot1.webp differ diff --git a/public/screenshot2.webp b/public/screenshot2.webp new file mode 100644 index 000000000..e7fc25e2d Binary files /dev/null and b/public/screenshot2.webp differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 000000000..4017ee3b1 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,62 @@ +{ + "name": "Wekan", + "short_name": "Wekan", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/Square150x150Logo.scale-100.png", + "sizes": "150x150", + "type": "image/png" + }, + { + "src": "/Square44x44Logo.scale-100.png", + "sizes": "44x44", + "type": "image/png" + }, + { + "src": "/StoreLogo.scale-100.png", + "sizes": "50x50", + "type": "image/png" + }, + { + "src": "/maskable_icon.png", + "sizes": "474x474", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/monochrome-icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "monochrome" + } + ], + "screenshots" : [ + { + "src": "/screenshot1.webp", + "sizes": "1280x720", + "type": "image/webp" + }, + { + "src": "/screenshot2.webp", + "sizes": "1280x720", + "type": "image/webp" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "start_url": "/sign-in", + "display": "standalone", + "orientation": "any", + "categories": ["productivity"], + "iarc_rating_id": "70d7c4a4-3e5a-4714-a7dc-fa006613ba96" +} diff --git a/public/svg-etc/manifest.json b/public/svg-etc/manifest.json new file mode 100644 index 000000000..ef9dbfd2f --- /dev/null +++ b/public/svg-etc/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "Wekan", + "short_name": "Wekan", + "description": "Open-Source kanban", + "icons": [ + { + "src": "wekan-logo-150.png", + "sizes": "150x150", + "type": "image/png" + }, + { + "src": "wekan-logo-150.svg", + "sizes": "150x150", + "type": "image/svg+xml" + }, + { + "src": "wekan-logo-256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "wekan-logo-256.svg", + "sizes": "256x256", + "type": "image/svg+xml" + }, + { + "src": "wekan-logo-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "wekan-logo-512.svg", + "sizes": "512x512", + "type": "image/svg+xml" + } + ], + "display": "fullscreen", + "background_color": "#dedede", + "theme_color": "#dedede", + "start_url": "/" +} diff --git a/public/wekan-favicon.png b/public/svg-etc/wekan-favicon.png similarity index 100% rename from public/wekan-favicon.png rename to public/svg-etc/wekan-favicon.png diff --git a/public/wekan-logo-150.png b/public/svg-etc/wekan-logo-150.png similarity index 100% rename from public/wekan-logo-150.png rename to public/svg-etc/wekan-logo-150.png diff --git a/public/wekan-logo-150.svg b/public/svg-etc/wekan-logo-150.svg similarity index 100% rename from public/wekan-logo-150.svg rename to public/svg-etc/wekan-logo-150.svg diff --git a/public/svg-etc/wekan-logo-256.png b/public/svg-etc/wekan-logo-256.png new file mode 100644 index 000000000..3f29a8974 Binary files /dev/null and b/public/svg-etc/wekan-logo-256.png differ diff --git a/public/svg-etc/wekan-logo-256.svg b/public/svg-etc/wekan-logo-256.svg new file mode 100644 index 000000000..2a9c5e881 --- /dev/null +++ b/public/svg-etc/wekan-logo-256.svg @@ -0,0 +1,203 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/svg-etc/wekan-logo-32.png b/public/svg-etc/wekan-logo-32.png new file mode 100644 index 000000000..c5bdffdac Binary files /dev/null and b/public/svg-etc/wekan-logo-32.png differ diff --git a/public/svg-etc/wekan-logo-512.png b/public/svg-etc/wekan-logo-512.png new file mode 100644 index 000000000..0bb904979 Binary files /dev/null and b/public/svg-etc/wekan-logo-512.png differ diff --git a/public/svg-etc/wekan-logo-512.svg b/public/svg-etc/wekan-logo-512.svg new file mode 100644 index 000000000..e945a40de --- /dev/null +++ b/public/svg-etc/wekan-logo-512.svg @@ -0,0 +1,200 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/wekan-logo.svg b/public/wekan-logo.svg new file mode 100644 index 000000000..da34a07ac --- /dev/null +++ b/public/wekan-logo.svg @@ -0,0 +1 @@ +wekan-1_2 \ No newline at end of file diff --git a/public/wekan-manifest.json b/public/wekan-manifest.json deleted file mode 100644 index ee223e8cb..000000000 --- a/public/wekan-manifest.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "Wekan", - "short_name": "Wekan", - "description": "The open-source kanban", - "lang": "en-US", - "icons": [ - { - "src": "/wekan-logo-150.png", - "type": "image/png", - "sizes": "150x150" - }, - { - "src": "/wekan-logo-150.svg", - "type": "image/svg+xml", - "sizes": "150x150" - } - ], - "display": "standalone", - "background_color": "#dedede", - "theme_color": "#dedede", - "start_url": "/" -} diff --git a/rebuild-wekan.bat b/rebuild-wekan.bat index 0a9a8eaca..07ca2493a 100644 --- a/rebuild-wekan.bat +++ b/rebuild-wekan.bat @@ -1,61 +1,64 @@ -@ECHO OFF - -REM NOTE: You can try this to install Meteor on Windows, it works: -REM https://github.com/zodern/windows-meteor-installer/ - -REM Installing Meteor with Chocolatey does not currently work. - -REM NOTE: THIS .BAT DOES NOT WORK !! -REM Use instead this webpage instructions to build on Windows: -REM https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows -REM Please add fix PRs, like config of MongoDB etc. - -md C:\repos -cd C:\repos - -REM Install chocolatey -@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" - -choco install -y git curl python2 dotnet4.5.2 nano mongodb-3 mongoclient meteor - -curl -O https://nodejs.org/dist/v12.16.1/node-v12.16.1-x64.msi -call node-v12.16.1-x64.msi - -call npm config -g set msvs_version 2015 -call meteor npm config -g set msvs_version 2015 - -call npm -g install npm -call npm -g install node-gyp -call npm -g install fibers -cd C:\repos -git clone https://github.com/wekan/wekan.git -cd wekan -git checkout edge -echo "Building Wekan." -REM del /S /F /Q packages -REM ## REPOS BELOW ARE INCLUDED TO WEKAN -REM md packages -REM cd packages -REM git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router -REM git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core -REM git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git -REM git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git -REM git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git -REM git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git -REM git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git -REM move meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc -REM move meteor-accounts-oidc/packages/switch_oidc wekan_oidc -REM del /S /F /Q meteor-accounts-oidc -REM sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js -cd .. -REM del /S /F /Q node_modules -call meteor npm install -REM del /S /F /Q .build -call meteor build .build --directory -copy fix-download-unicode\cfs_access-point.txt .build\bundle\programs\server\packages\cfs_access-point.js -cd .build\bundle\programs\server -call meteor npm install -REM cd C:\repos\wekan\.meteor\local\build\programs\server -REM del node_modules -cd C:\repos\wekan -call start-wekan.bat +@ECHO OFF + +REM NOTE: You can try this to install Meteor on Windows, it works: +REM https://github.com/zodern/windows-meteor-installer/ + +REM Installing Meteor with Chocolatey does not currently work. + +REM NOTE: THIS .BAT DOES NOT WORK !! +REM Use instead this webpage instructions to build on Windows: +REM https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows +REM Please add fix PRs, like config of MongoDB etc. + +md C:\repos +cd C:\repos + +REM Install chocolatey +@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" + +choco install -y git curl python2 dotnet4.5.2 nano mongodb-3 mongoclient meteor + +curl -O https://nodejs.org/dist/v12.22.3/node-v12.22.3-x64.msi +call node-v12.22.3-x64.msi + +call npm config -g set msvs_version 2015 +call meteor npm config -g set msvs_version 2015 + +call npm -g install npm +call npm -g install node-gyp +call npm -g install fibers +cd C:\repos +git clone https://github.com/wekan/wekan.git +cd wekan +git checkout edge +echo "Building Wekan." +REM del /S /F /Q packages +REM ## REPOS BELOW ARE INCLUDED TO WEKAN +REM md packages +REM cd packages +REM git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router +REM git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core +REM git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git +REM git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git +REM git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git +REM git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git +REM git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git +REM move meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc +REM move meteor-accounts-oidc/packages/switch_oidc wekan_oidc +REM del /S /F /Q meteor-accounts-oidc +REM sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js +cd .. +REM del /S /F /Q node_modules +call meteor npm install +REM del /S /F /Q .build +call meteor build .build --directory +copy fix-download-unicode\cfs_access-point.txt .build\bundle\programs\server\packages\cfs_access-point.js +REM ## Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. +del /S /F /Q rm .build/bundle/programs/web.browser.legacy +REM ## Install some NPM packages +cd .build\bundle\programs\server +call meteor npm install +REM cd C:\repos\wekan\.meteor\local\build\programs\server +REM del node_modules +cd C:\repos\wekan +call start-wekan.bat diff --git a/rebuild-wekan.sh b/rebuild-wekan.sh index 957e634f5..58a8ea502 100755 --- a/rebuild-wekan.sh +++ b/rebuild-wekan.sh @@ -5,66 +5,15 @@ echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correct echo " You can still use any other locale as your main locale." #Below script installs newest node 8.x for Debian/Ubuntu/Mint. -#NODE_VERSION=12.16.1 -#X64NODE="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" function pause(){ read -p "$*" } -function cprec(){ - if [[ -d "$1" ]]; then - if [[ ! -d "$2" ]]; then - sudo mkdir -p "$2" - fi - - for i in $(ls -A "$1"); do - cprec "$1/$i" "$2/$i" - done - else - sudo cp "$1" "$2" - fi -} - -# sudo npm doesn't work right, so this is a workaround -function npm_call(){ - TMPDIR="/tmp/tmp_npm_prefix" - if [[ -d "$TMPDIR" ]]; then - rm -rf $TMPDIR - fi - mkdir $TMPDIR - NPM_PREFIX="$(npm config get prefix)" - npm config set prefix $TMPDIR - npm "$@" - npm config set prefix "$NPM_PREFIX" - - echo "Moving files to $NPM_PREFIX" - for i in $(ls -A $TMPDIR); do - cprec "$TMPDIR/$i" "$NPM_PREFIX/$i" - done - rm -rf $TMPDIR -} - -#function wekan_repo_check(){ -## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN -# git_remotes="$(git remote show 2>/dev/null)" -# res="" -# for i in $git_remotes; do -# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')" -# if [[ "$res" == "wekan/wekan" ]]; then -# break -# fi -# done -# -# if [[ "$res" != "wekan/wekan" ]]; then -# echo "$PWD is not a wekan repository" -# exit; -# fi -#} - echo PS3='Please enter your choice: ' -options=("Install Wekan dependencies" "Build Wekan" "Quit") +options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Quit") + select opt in "${options[@]}" do case $opt in @@ -73,13 +22,13 @@ do if [[ "$OSTYPE" == "linux-gnu" ]]; then echo "Linux"; # Debian, Ubuntu, Mint - sudo apt-get install -y build-essential gcc g++ make git curl wget + sudo apt-get install -y build-essential gcc g++ make git curl wget npm p7zip-full # npm nodejs #sudo npm -g install npm - curl -0 -L https://npmjs.org/install.sh | sudo sh - sudo chown -R $(id -u):$(id -g) $HOME/.npm + #curl -0 -L https://npmjs.org/install.sh | sudo sh + #sudo chown -R $(id -u):$(id -g) $HOME/.npm sudo npm -g install n - sudo n 12.16.1 + sudo n 12.22.3 #curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - #sudo apt-get install -y nodejs elif [[ "$OSTYPE" == "darwin"* ]]; then @@ -106,34 +55,20 @@ do exit; fi - ## Latest npm with Meteor 1.8.x - npm_call -g install npm - npm_call -g install node-gyp - # Latest fibers for Meteor 1.8.x + ## Latest npm with Meteor 1.8.x + sudo npm -g install npm + sudo npm -g install node-gyp + # Latest fibers for Meteor 1.8.x sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp - npm_call -g install fibers - # Install Meteor, if it's not yet installed - curl https://install.meteor.com | bash - sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor + sudo npm -g install fibers + # Install Meteor, if it's not yet installed + curl https://install.meteor.com | bash + #sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor break ;; - "Build Wekan") + + "Build Wekan") echo "Building Wekan." - #wekan_repo_check - # REPOS BELOW ARE INCLUDED TO WEKAN REPO - #rm -rf packages/kadira-flow-router packages/meteor-useraccounts-core packages/meteor-accounts-cas packages/wekan-ldap packages/wekan-ldap packages/wekan-scrfollbar packages/meteor-accounts-oidc packages/markdown - #mkdir packages - #cd packages - #git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router - #git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core - #git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git - #git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git - #git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git - #git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git - #git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git - #mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc - #mv meteor-accounts-oidc/packages/switch_oidc wekan_oidc - #rm -rf meteor-accounts-oidc #if [[ "$OSTYPE" == "darwin"* ]]; then # echo "sed at macOS"; # sed -i '' 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js @@ -142,29 +77,60 @@ do # sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js #fi #cd .. - sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor - rm -rf node_modules .meteor/local + #sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor + cd ~/repos/wekan + rm -rf node_modules .meteor/local .build + chmod u+w *.json npm install - rm -rf .build meteor build .build --directory - cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js - #Removed binary version of bcrypt because of security vulnerability that is not fixed yet. - #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac - #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c - #cd ~/repos/wekan/.build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt - #rm -rf node_modules/bcrypt - #meteor npm install bcrypt - cd .build/bundle/programs/server + rm -rf ~/repos/wekan/.build/bundle/programs/web.browser.legacy + cd ~/repos/wekan/.build/bundle/programs/server rm -rf node_modules + chmod u+w *.json npm install - #meteor npm install bcrypt - cd ../../../.. + # Cleanup + cd ~/repos/wekan/.build/bundle + find . -type d -name '*-garbage*' | xargs rm -rf + find . -name '*phantom*' | xargs rm -rf + find . -name '.*.swp' | xargs rm -f + find . -name '*.swp' | xargs rm -f + cd ~/repos/wekan + # Add fibers multi arch + #cd .build/bundle/programs/server/node_modules/fibers/bin + #curl https://releases.wekan.team/fibers-multi.7z -o fibers-multi.7z + #7z x fibers-multi.7z + #rm fibers-multi.7z + #cd ../../../../../../.. echo Done. break ;; - "Quit") + + "Run Meteor for dev on http://localhost:4000") + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 break - ;; - *) echo invalid option;; + ;; + + "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000") + IPADDRESS=$(ip a | grep 'noprefixroute' | grep 'inet ' | cut -d: -f2 | awk '{ print $2}' | cut -d '/' -f 1) + echo "Your IP address is $IPADDRESS" + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 + break + ;; + + "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT") + ip address + echo "From above list, what is your IP address?" + read IPADDRESS + echo "On what port you would like to run Wekan?" + read PORT + echo "ROOT_URL=http://$IPADDRESS:$PORT" + WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --exclude-archs web.browser.legacy,web.cordova --port $PORT + break + ;; + + "Quit") + break + ;; + *) echo invalid option;; esac done diff --git a/releases/README.md b/releases/README.md index 86601ad2b..1246143ca 100644 --- a/releases/README.md +++ b/releases/README.md @@ -1,12 +1,39 @@ ## Wekan release scripts -Sorry about the mess. I try to cleanup it sometime. +Release process, for example version 4.94: + +1) Build x64 bundle and upload to x2/a/s/o: +=========================================== +./release.sh 4.94 + + +2) Build bundles at servers +=========================== +arm64: + ssh a + ./maintainer-make-release-a.sh 4.94 + +s390x: + ssh s + ./maintainer-make-release-s.sh 4.94 + +openpower: + ssh o + ./maintainer-make-release-o.sh 4.94 + + +3) Download bundles and upload to x2 releases: +============================================== +./releases/up.sh + +4) At x2, do release: +===================== +ssh x2 +cd /var/snap/wekan/common +./release-x2.sh 4.93 4.94 -I usually use these: -- release.sh -- release-sandstorm.sh -- release-snap.sh https://github.com/wekan/wekan-snap/wiki/Making-releases-from-source https://github.com/wekan/wekan-maintainer/wiki/Building-Wekan-for-Sandstorm + diff --git a/releases/changelog.sh b/releases/changelog.sh new file mode 100755 index 000000000..902ae2df0 --- /dev/null +++ b/releases/changelog.sh @@ -0,0 +1 @@ +git add CHANGELOG.md && git commit -n -m "Updated ChangeLog." && git push diff --git a/releases/clone-release-repos.sh b/releases/clone-release-repos.sh new file mode 100755 index 000000000..dac9eb1d8 --- /dev/null +++ b/releases/clone-release-repos.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# With this, clone all release related repos. + +# 1) Check that this script has no parameters +if [ $# -ne 0 ] + then + echo "Syntax, no parameters:" + echo " ./releases/clone-release-repos.sh" + exit 1 +fi + +# 2) Create directories, clone repos +mkdir ../w +cd ../w +git clone git@github.com:wekan/wekan.github.io.git +git clone git@github.com:wekan/wekan-ondra.git +git clone git@github.com:wekan/wekan-gantt-gpl.git + +# 3) Set upstreams +cd wekan-ondra +git remote add upstream https://github.com/wekan/wekan + +cd ../wekan-gantt-gpl +git remote add upstream https://github.com/wekan/wekan + +# 4) Go back to Wekan repo directory +cd ../wekan + +echo "Release repos ondra, gantt-gpl, and website cloned and upstreams set." diff --git a/releases/disable-sandstorm.sh b/releases/disable-sandstorm.sh new file mode 100755 index 000000000..1cf17df1c --- /dev/null +++ b/releases/disable-sandstorm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo sandstorm stop +sudo systemctl disable sandstorm diff --git a/releases/install-sandstorm.sh b/releases/install-sandstorm.sh new file mode 100755 index 000000000..db89fba65 --- /dev/null +++ b/releases/install-sandstorm.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "INSTALLING WEKAN SANDSTORM VERSION RELATED FILES:" +sudo apt-get -y install p7zip-full wget curl +cd ~ +wget https://releases.wekan.team/meteor-spk/projects.7z +7z x projects.7z +rm projects.7z +echo "export PATH=\$PATH:~/projects/meteor-spk/meteor-spk-0.5.1" >> ~/.bashrc +source ~/.bashrc +echo "INSTALL DEV VERSION OF SANDSTORM:" +curl https://install.sandstorm.io | bash +cd ~/repos/wekan diff --git a/releases/maintainer-make-bundle-a.sh b/releases/maintainer-make-bundle-a.sh new file mode 100755 index 000000000..71ef923a5 --- /dev/null +++ b/releases/maintainer-make-bundle-a.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# This script is only for Wekan maintainer to +# convert x64 bundle to arm64 bundle. + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./maintainer-make-bundle-a.sh 5.10" + exit 1 +fi + +cd ~/repos +rm -rf bundle + +unzip wekan-$1.zip + +sudo chown wekan:wekan bundle -R +sudo apt -y install libcurl4-openssl-dev + +sudo rm -f /home/wekan/repos/bundle/programs/server/node_modules/.bin/node-pre-gyp +sudo rm -f /home/wekan/repos/bundle/programs/server/node_modules/.bin/node-gyp +sudo rm -rf /home/wekan/repos/bundle/programs/server/npm/node_modules/meteor/lucasantoniassi_accounts-lockout/node_modules/.phantomjs-prebuilt-garbage-* +sudo rm -rf /home/wekan/repos/bundle/programs/server/node_modules/.bin/* +sudo rm -rf /home/wekan/repos/bundle/programs/server/node_modules/node-pre-gyp/node_modules/.bin/* +sudo rm -rf /home/wekan/repos/bundle/programs/server/node_modules/node-gyp/node_modules/.bin/* +sudo rm -rf /home/wekan/repos/bundle/programs/server/npm/node_modules/meteor/ostrio_files/node_modules/request-libcurl/.node_modules-garbage* + +cd bundle/programs/server +chmod u+w *.json +npm uninstall fibers node-gyp node-pre-gyp @mapbox/node-pre-gyp +npm install +npm install node-gyp +npm install @mapbox/node-pre-gyp +npm install fibers + +cd /home/wekan/repos/bundle +find . -type d -name '*-garbage*' | xargs rm -rf +find . -name '*phantom*' | xargs rm -rf +find . -name '.*.swp' | xargs rm -f +find . -name '*.swp' | xargs rm -f + +cd /home/wekan/repos + +zip -r wekan-$1-arm64.zip bundle diff --git a/releases/maintainer-make-bundle-o.sh b/releases/maintainer-make-bundle-o.sh new file mode 100755 index 000000000..51004dc4f --- /dev/null +++ b/releases/maintainer-make-bundle-o.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# This script is only for Wekan maintainer to +# convert x64 bundle to ppc64le bundle. + +# 1) Check that there is only one parameter +# of Wekan version number + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./maintainer-make-bundle-o.sh 5.10" + exit 1 +fi + +# 2) Build bundle + +cd /home/ubuntu +rm -rf bundle +#wget https://releases.wekan.team/wekan-$1.zip +unzip wekan-$1.zip +cd /home/ubuntu/bundle/programs/server +chmod u+w *.json +cd /home/ubuntu/bundle/programs/server/node_modules/fibers +node build.js +cd /home/ubuntu +#cp -pR /home/ubuntu/node-fibers/bin/linux-ppc64-72-glibc bundle/programs/server/node_modules/fibers/bin/ +cd bundle +find . -type d -name '*-garbage*' | xargs rm -rf +find . -name '*phantom*' | xargs rm -rf +find . -name '.*.swp' | xargs rm -f +find . -name '*.swp' | xargs rm -f +cd .. +zip -r wekan-$1-ppc64le.zip bundle diff --git a/releases/maintainer-make-bundle-s.sh b/releases/maintainer-make-bundle-s.sh new file mode 100755 index 000000000..eae1f82f3 --- /dev/null +++ b/releases/maintainer-make-bundle-s.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# This script is only for Wekan maintainer to +# convert x64 bundle to s390x bundle. + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./maintainer-make-bundle-s.sh 5.10" + exit 1 +fi + +cd /home/linux1 +rm -rf bundle +unzip wekan-$1.zip +cd /home/linux1/bundle/programs/server +chmod u+w *.json +cd /home/linux1/bundle/programs/server/node_modules/fibers +node build.js +cd /home/linux1 +#cp -pR /home/linux1/node-fibers/bin/linux-s390x-83-glibc bundle/programs/server/node_modules/fibers/bin/ +cd bundle +find . -type d -name '*-garbage*' | xargs rm -rf +find . -name '*phantom*' | xargs rm -rf +find . -name '.*.swp' | xargs rm -f +find . -name '*.swp' | xargs rm -f +cd .. +zip -r wekan-$1-s390x.zip bundle diff --git a/releases/node-update-local.sh b/releases/node-update-local.sh new file mode 100755 index 000000000..de68f5c16 --- /dev/null +++ b/releases/node-update-local.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Update node version. + +# Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 2 ] + then + echo "Syntax with Node old and new version number:" + echo " ./node-update-local.sh 12.21.0 12.22.0" + exit 1 +fi + +NODE_VERSION=$(node -v) + +# If installed node is not newest version +if [ $NODE_VERSION != v$2 ]; then + echo "8) Upgrading installed node to newest version" + sudo n $2 +else + echo "8) Installed node is already newest version" +fi + +# If Sandstorm node does not exist +SANDSTORM_NODE=~/projects/meteor-spk/meteor-spk-0.5.1/meteor-spk.deps/bin/node + +#if [[ -f "$SANDSTORM_NODE" ]]; then +# echo "9) Installing local Sandstorm develoment version" +# ~/repos/wekan/releases/install-sandstorm.sh +#else +# echo "9) Local Sandstorm is already installed" +#fi + +SANDSTORM_NODE_VERSION=$($SANDSTORM_NODE -v) +PROJECTS_ARCHIVE=~/projects.7z + +# If installed Sandstorm node is not newest version +if [ $SANDSTORM_NODE != v$2 ]; then + echo "9) Copy previously updated local node to Sandstorm node" + cp /usr/local/bin/node ~/projects/meteor-spk/meteor-spk-0.5.1/meteor-spk.deps/bin/ +# echo "11) Install 7zip" +# sudo apt-get -y install p7zip-full +# # If projects.7z exists, delete it +# if [[ -f "$PROJECTS_ARCHIVE" ]] then; +# echo "12) Deleting existing project.7z archive" +# rm $PROJECTS_ARCHIVE +# else +# echo "12) There is no existin project.7z archive" +# fi +# echo "13) Archive projects.7z directory" +# 7z a projects.7z projects +# echo "14) Upload projects.7z archive to webserver" +# scp projects.7z x2:/var/snap/wekan/common/releases.wekan.team/meteor-spk/ +fi + +echo "Done." diff --git a/releases/node-update.sh b/releases/node-update.sh new file mode 100755 index 000000000..af4eac77e --- /dev/null +++ b/releases/node-update.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Update node version. + +# Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 2 ] + then + echo "Syntax with Node old and new version number:" + echo " ./node-update.sh 12.21.0 12.22.0" + exit 1 +fi + +# With replacing longer strings than only version number, +# trying to make sure only Node.js version is updated. + +echo "1) Updating Snap node" +sed -i "s|node-engine: $1|node-engine: $2|g" ~/repos/wekan/snapcraft.yaml +sed -i "s|node-engine: $1|node-engine: $2|g" ~/repos/wekan/.future-snap/snapcraft.yaml +sed -i "s|node-engine: $1|node-engine: $2|g" ~/repos/wekan/.future-snap/broken-snapcraft.yaml + +echo "2) Updating Docker node" +sed -i "s|NODE_VERSION=v$1|NODE_VERSION=v$2|g" ~/repos/wekan/Dockerfile +sed -i "s|NODE_VERSION=v$1|NODE_VERSION=v$2|g" ~/repos/wekan/Dockerfile.arm64v8 +sed -i "s|NODE_VERSION=v$1|NODE_VERSION=v$2|g" ~/repos/wekan/.devcontainer/Dockerfile + +echo "3) Updating Rebuild scripts..." +sed -i "s|sudo n $1|sudo n $2|g" ~/repos/wekan/rebuild-wekan.sh +sed -i "s|nodejs.org/dist/v$1|nodejs.org/dist/v$2|g" ~/repos/wekan/rebuild-wekan.bat +sed -i "s|node-v$1|node-v$2|g" ~/repos/wekan/rebuild-wekan.bat + +echo "4) Updating Stacksmith" +sed -i "s|$1|$2|g" ~/repos/wekan/stacksmith/user-scripts/build.sh + +echo "5) Updating Travis" +sed -i "s|$1|$2|g" ~/repos/wekan/.travis.yml + +#echo "6) Adding changes to be committed." +git add snapcraft.yaml .future-snap/snapcraft.yaml .future-snap/broken-snapcraft.yaml \ +Dockerfile Dockerfile.arm64v8 .devcontainer/Dockerfile rebuild-wekan.sh \ +rebuild-wekan.bat stacksmith/user-scripts/build.sh .travis.yml + +echo "7) Commit changes and push to GitHub" +git commit -n -m "Updated to Node.js v$2. Thanks to Node.js developers." +git push diff --git a/releases/node-version.sh b/releases/node-version.sh new file mode 100755 index 000000000..a5089fdba --- /dev/null +++ b/releases/node-version.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cat ~/repos/wekan/Dockerfile | grep NODE_VERSION=v | sed 's|\\||g' - | sed 's| ||g' - | sed 's|NODE_VERSION=v||g' - diff --git a/releases/rebuild-docs-install-deps.sh b/releases/rebuild-docs-install-deps.sh deleted file mode 100755 index 4f9a2a67c..000000000 --- a/releases/rebuild-docs-install-deps.sh +++ /dev/null @@ -1,21 +0,0 @@ -# Generate docs. - -# extract the OpenAPI specification -sudo apt-get install python3-pip -sudo pip3 install -U setuptools wheel -sudo npm install -g api2html@0.3.3 -sudo npm install -g --unsafe-perm api2html@0.3.0 -sudo npm install -g --unsafe-perm mkdirp -mkdir -p ~/python -cd ~/python -git clone --depth 1 -b master https://github.com/Kronuz/esprima-python -cd ~/python/esprima-python -sudo python3 setup.py install --record files.txt -cd ~/repos/wekan -mkdir -p public/api -python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml -api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml - -# Copy docs to bundle -#cp -pR ./public/api ~/repos/wekan/.build/bundle/programs/web.browser/app/ -#cp -pR ./public/api ~/repos/wekan/.build/bundle/programs/web.browser.legacy/app/ diff --git a/releases/rebuild-docs.sh b/releases/rebuild-docs.sh index 2ac46eb34..c7938793f 100755 --- a/releases/rebuild-docs.sh +++ b/releases/rebuild-docs.sh @@ -1,9 +1,41 @@ -# Generate docs. +# Extract the OpenAPI specification. -#mkdir -p public/api -python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml +# 1) Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./rebuild-docs.sh 5.10" + exit 1 +fi + +# 2) If esprima-python directory does not exist, +# install dependencies. + +if [ ! -d ~/python/esprima-python ]; then + sudo apt-get -y install python3-pip + sudo pip3 install -U setuptools wheel + sudo npm install -g api2html + mkdir -p ~/python + cd ~/python + git clone --depth 1 -b master https://github.com/Kronuz/esprima-python + cd ~/python/esprima-python + sudo python3 setup.py install --record files.txt +fi + +# 2) Go to Wekan repo directory +cd ~/repos/wekan + +# 3) Create api docs directory, if it does not exist +if [ ! -d public/api ]; then + mkdir -p public/api +fi + +# 4) Generate docs. +#python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml +python3 ./openapi/generate_openapi.py --release v$1 > ./public/api/wekan.yml api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml # Copy docs to bundle #cp -pR ./public/api ~/repos/wekan/.build/bundle/programs/web.browser/app/ -#cp -pR ./public/api ~/repos/wekan/.build/bundle/programs/web.browser.legacy/app/ diff --git a/releases/rebuild-release.sh b/releases/rebuild-release.sh index f39648852..02b862d0e 100755 --- a/releases/rebuild-release.sh +++ b/releases/rebuild-release.sh @@ -11,6 +11,8 @@ meteor npm install rm -rf .build METEOR_PROFILE=100 meteor build .build --directory cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js +# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. +rm -rf .build/bundle/programs/web.browser.legacy cd .build/bundle/programs/server rm -rf node_modules meteor npm install diff --git a/releases/release-bundle.sh b/releases/release-bundle.sh index b309ee829..309c041a0 100755 --- a/releases/release-bundle.sh +++ b/releases/release-bundle.sh @@ -1,7 +1,20 @@ cd ~/repos/wekan +sudo apt-get -y install parallel ./releases/rebuild-release.sh #./releases/delete-phantomjs.sh cd ~/repos/wekan/.build zip -r wekan-$1.zip bundle -scp wekan-$1.zip x2:/var/snap/wekan/common/releases.wekan.team/ + +{ + scp ~/repos/wekan/releases/maintainer-make-bundle-a.sh a:/home/wekan/repos/maintainer-make-bundle.sh + scp ~/repos/wekan/releases/maintainer-make-bundle-s.sh s:/home/linux1/maintainer-make-bundle.sh + scp ~/repos/wekan/releases/maintainer-make-bundle-o.sh o:/home/ubuntu/maintainer-make-bundle.sh + scp wekan-$1.zip x2:/var/snap/wekan/common/releases.wekan.team/ + scp wekan-$1.zip a:/home/wekan/repos/ + scp wekan-$1.zip s:/home/linux1/ + scp wekan-$1.zip o:/home/ubuntu/ +} | parallel -k + cd .. + +echo "x64 bundle and arm64/s390x/ppc64le build scripts uploaded to x2/a/s/o." diff --git a/releases/release-ln.sh b/releases/release-ln.sh new file mode 100755 index 000000000..53df95094 --- /dev/null +++ b/releases/release-ln.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./ln.sh 5.10" + exit 1 +fi + +echo "Release ln: x64" +cd releases.wekan.team +rm wekan-latest-x64.zip +ln -s wekan-$1.zip wekan-latest-x64.zip +cd .. + +echo "Release ln: arm64" +cd releases.wekan.team/raspi3 +rm wekan-latest-arm64.zip +ln -s wekan-$1.zip wekan-latest-arm64.zip +cd ../.. + +echo "Release ln: s390x" +cd releases.wekan.team/s390x +rm wekan-latest-s390x.zip +ln -s wekan-$1.zip wekan-latest-s390x.zip +cd ../.. + +echo "Release ln: ppc64le" +cd releases.wekan.team/ppc64le +rm wekan-latest-ppc64le.zip +ln -s wekan-$1.zip wekan-latest-ppc64le.zip +cd ../.. diff --git a/releases/release-ondra-1.sh b/releases/release-ondra-1.sh new file mode 100755 index 000000000..986f8f0c0 --- /dev/null +++ b/releases/release-ondra-1.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Release script for wekan-ondra and wekan-gantt-gpl +# part 1. After this merge and fix merge conflicts, and part 2. + +# 1) Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./release-ondra-2.sh 5.10" + exit 1 +fi + +# 2) Get up +git fetch upstream +git merge upstream/master diff --git a/releases/release-ondra-2.sh b/releases/release-ondra-2.sh new file mode 100755 index 000000000..7278c08dd --- /dev/null +++ b/releases/release-ondra-2.sh @@ -0,0 +1,20 @@ +#/bin/bash + +# Release script for wekan-ondra and wekan-gantt-gpl +# part 2. Before these, part 1 and merge and fix merge conflicts. + +# 1) Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./release-ondra-2.sh 5.10" + exit 1 +fi + +# 2) Move Wekan version tag to be newest after merge +# and push to repo. +git tag --force v$1 HEAD +git push --tags --force +git push --follow-tags diff --git a/releases/release-sandstorm.sh b/releases/release-sandstorm.sh index 997e46c4e..797eb4710 100755 --- a/releases/release-sandstorm.sh +++ b/releases/release-sandstorm.sh @@ -1,64 +1,37 @@ -# Usage: ./release.sh 1.36 +#!/bin/bash -# Delete old stuff -#cd ~/repos/wekan -#./releases/release-cleanup.sh +# Release Sandstorm Wekan. -# Build Source -#cd ~/repos/wekan -#./releases/rebuild-release.sh +# 1) Check that there is only one parameter +# of Sandstorm Wekan version number: -REPODIR=/home/wekan/repos -WEKANDIR=/home/wekan/repos/wekan -OLDDIR=/home/wekan/repos/sandstorm-build -METEDIR=/home/wekan/repos/wekan/.sandstorm-meteor-1.8 +if [ $# -ne 1 ] + then + echo "Syntax with new Sandstorm Wekan version number:" + echo " ./release-sandstorm.sh 5.10.0" + exit 1 +fi # Ensure sudo access sudo echo . -# Build Sandstorm -cd $REPODIR -rm -rf $WEKANDIR -git clone git@github.com:wekan/wekan.git -cd $WEKANDIR -# Use Meteor 1.8.x and Node 8.17.0 -sudo n 8.17.0 -#sudo rm -rf /root/.cache/node-gyp/8.17.0 -sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp -#sudo npm -g uninstall node-gyp node-pre-gyp fibers -#./releases/rebuild-release.sh -rm -rf $OLDDIR -mkdir $OLDDIR -mv .meteor $OLDDIR/ -cp -pR .snap-meteor-1.8 $OLDDIR/ -mv $METEDIR/.meteor . -mv $METEDIR/package.json . -mv $METEDIR/package-lock.json . -# Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those -mv $METEDIR/cfs_access-point.txt fix-download-unicode/ -mv $METEDIR/export.js models/ -mv $METEDIR/wekanCreator.js models/ -mv $METEDIR/ldap.js packages/wekan-ldap/server/ldap.js -mv $METEDIR/oidc_server.js packages/wekan-oidc/oidc_server.js -rm -rf $METEDIR -# Build Wekan -./releases/rebuild-release.sh -# Build bundle with Meteor 1.8.x and Node 8.17.0 -cd .build/bundle/programs/server -npm install node-gyp node-pre-gyp fibers@2.0.0 -cd $WEKANDIR -# Build Sandstorm -meteor-spk pack wekan-$1.spk -#spk publish wekan-$1.spk -#scp wekan-$1.spk x2:/var/snap/wekan/common/releases.wekan.team/ -#mv wekan-$1.spk .. -#sudo rm -rf .meteor-spk -# Back to Meteor 1.9 and Node 12.14.1 -#sudo n 12.14.1 -#sudo rm -rf .meteor -#mv ../sandstorm-build/.meteor . -#mv ../sandstorm-build/.snap-meteor-1.8 . -#rmdir ../sandstorm-build -# Delete old stuff -#cd ~/repos/wekan -#./releases/release-cleanup.sh +# Delete old temporary build directory +rm -rf ~/repos/wekan/.meteor-spk + +# Start and update local Sandstorm dev version +sudo systemctl enable sandstorm +sudo sandstorm start +sudo sandstorm update + +# Build Sandstorm Wekan +cd ~/repos/wekan +meteor-spk pack wekan-$1.spk + +# Publish Sandstorm Wekan to exprimental App Market +spk publish wekan-$1.spk + +# Upload spk to https://releases.wekan.team/sandstorm/ +scp wekan-$1.spk x2:/var/snap/wekan/common/releases.wekan.team/sandstorm/ + +# Delete old temporary build directory +rm -rf ~/repos/wekan/.meteor-spk diff --git a/releases/release-website.sh b/releases/release-website.sh new file mode 100755 index 000000000..f738c6489 --- /dev/null +++ b/releases/release-website.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Release website with new Wekan version number +# and new API docs. + +# 1) Check that there is only 2 parameters +# of Wekan previous and new version number: + +if [ $# -ne 2 ] + then + echo "Syntax with Wekan previous and new version number:" + echo " ./release-website.sh 5.09 5.10" + exit 1 +fi + +# 2) Go to website directory +cd ~/repos/w/wekan.github.io + +# 3) Get latest changes to website +git pull + +# 4) Change version number in website +sed -i "s|>v$1<\/span>|>v$2<\/span>|g" index.html + +# 5) Change version number in API docs index page +cd api +sed -i "s|v$1|v$2|g" index.html + +# 6) Create directory for new docs +mkdir v$2 + +# 7) Go to new docs directory +cd v$2 + +# 8) Copy new docs from Wekan repo to new docs directory +cp ~/repos/wekan/public/api/* . + +# 9) Move wekan.html to index.html +mv wekan.html index.html + +# 10) Go to docs repo +cd ~/repos/w/wekan.github.io + +# 11) Commit all changes to git and push website changes live +git add --all +git commit -m "v$2" +git push diff --git a/releases/release-x2.sh b/releases/release-x2.sh new file mode 100755 index 000000000..8530adf88 --- /dev/null +++ b/releases/release-x2.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +if [ $# -ne 2 ] + then + echo "Syntax with Wekan old and new version number:" + echo " ./release.sh 5.10 5.11" + exit 1 +fi + +echo "Release: x64" +cd releases.wekan.team +mv wekan-$1.zip /data2/old-releases/ +sha256sum wekan-$2.zip >> SHA256SUMS.txt +mv SHA256SUMS.txt ../x64-SHA256SUMS.txt +cat ../x64-SHA256SUMS.txt | grep -v wekan-$1.zip > SHA256SUMS.txt +rm wekan-latest-x64.zip +ln -s wekan-$1.zip wekan-latest-x64.zip +cd .. + +echo "Release: arm64" +cd releases.wekan.team/raspi3 +mv wekan-$1-arm64.zip /data2/old-releases/raspi3/ +sha256sum wekan-$2-arm64.zip >> SHA256SUMS.txt +mv SHA256SUMS.txt ../../arm64-SHA256SUMS.txt +cat ../../arm64-SHA256SUMS.txt | grep -v wekan-$1-arm64.zip > SHA256SUMS.txt +rm wekan-latest-arm64.zip +ln -s wekan-$1.zip wekan-latest-arm64.zip +cd ../.. + +echo "Release: s390x" +cd releases.wekan.team/s390x +mv wekan-$1-s390x.zip /data2/old-releases/s390x/ +sha256sum wekan-$2-s390x.zip >> SHA256SUMS.txt +mv SHA256SUMS.txt ../../s390x-SHA256SUMS.txt +cat ../../s390x-SHA256SUMS.txt | grep -v wekan-$1-s390x.zip > SHA256SUMS.txt +rm wekan-latest-s390x.zip +ln -s wekan-$1.zip wekan-latest-s390x.zip +cd ../.. + +echo "Release: ppc64le" +cd releases.wekan.team/ppc64le +mkdir -p /data2/old-releases/ppc64le +mv wekan-$1-ppc64le.zip /data2/old-releases/ppc64le/ +sha256sum wekan-$2-ppc64le.zip >> SHA256SUMS.txt +mv SHA256SUMS.txt ../../ppc64le-SHA256SUMS.txt +cat ../../ppc64le-SHA256SUMS.txt | grep -v wekan-$1-ppc64le.zip > SHA256SUMS.txt +rm wekan-latest-ppc64le.zip +ln -s wekan-$1.zip wekan-latest-ppc64le.zip +cd ../.. diff --git a/releases/release.sh b/releases/release.sh index aca9775d8..6d8dfe394 100755 --- a/releases/release.sh +++ b/releases/release.sh @@ -1,9 +1,33 @@ -# Usage: ./release.sh 1.36 +#!/bin/bash -# Build Bundle +# Release script for wekan. + +# 1) Check that there is only one parameter +# of Wekan version number: + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./release.sh 5.10" + exit 1 +fi + +# 2) Commit and push version number changes +cd ~/repos/wekan +git add --all +git commit -m "v$1" +git push + +# 3) Add release tag +~/repos/wekan/releases/add-tag.sh v$1 + +# 4) Push to repo +git push + +# 5) Build Bundle ~/repos/wekan/releases/release-bundle.sh $1 -# Build Sandstorm +# 6) Build Sandstorm ~/repos/wekan/releases/release-sandstorm.sh $1 # Build Snap diff --git a/releases/stable-tag.sh b/releases/stable-tag.sh new file mode 100755 index 000000000..65e99f05c --- /dev/null +++ b/releases/stable-tag.sh @@ -0,0 +1,3 @@ +git tag --force stable HEAD +git push --tags --force +git push --follow-tags diff --git a/releases/translations/pull-translations.sh b/releases/translations/pull-translations.sh index b97f5a8de..1d00db994 100755 --- a/releases/translations/pull-translations.sh +++ b/releases/translations/pull-translations.sh @@ -3,6 +3,15 @@ cd ~/repos/wekan echo "Arabic:" tx pull -f -l ar +echo "Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo):" +tx pull -f -l ar_EG + +echo "Armenian:" +tx pull -f -l hy + +echo "Basque:" +tx pull -f -l eu + echo "Bulgarian:" tx pull -f -l bg_BG @@ -12,20 +21,26 @@ tx pull -f -l br echo "Catalan:" tx pull -f -l ca +echo "Chinese (China):" +tx pull -f -l zh_CN + +echo "Chinese (Hong Kong):" +tx pull -f -l zh_HK + +echo "Chinese (Taiwan):" +tx pull -f -l zh_TW + +echo "Croatian:" +tx pull -f -l hr + echo "Czech:" tx pull -f -l cs echo "Danish:" tx pull -f -l da -echo "Georgian:" -tx pull -f -l ka - -echo "German:" -tx pull -f -l de - -echo "Hindi:" -tx pull -f -l hi +echo "Dutch:" +tx pull -f -l nl echo "Esperanto:" tx pull -f -l eo @@ -33,24 +48,6 @@ tx pull -f -l eo echo "English (United Kingdom):" tx pull -f -l en_GB -echo "Greek:" -tx pull -f -l el - -echo "Slovenian:" -tx pull -f -l sl_SI - -echo "Spanish:" -tx pull -f -l es - -echo "Spanish (Argentina):" -tx pull -f -l es_AR - -echo "Basque:" -tx pull -f -l eu - -echo "Persian:" -tx pull -f -l fa - echo "Finnish:" tx pull -f -l fi @@ -60,14 +57,35 @@ tx pull -f -l fr echo "Galician:" tx pull -f -l gl +echo "Georgian:" +tx pull -f -l ka + +echo "German:" +tx pull -f -l de + +echo "German (Switzerland) => Deutsch (Schweiz):" +tx pull -f -l de_CH + +echo "Greek:" +tx pull -f -l el + echo "Hebrew:" tx pull -f -l he echo "Hungarian:" tx pull -f -l hu_HU -echo "Armenian:" -tx pull -f -l hy +echo "Hindi:" +tx pull -f -l hi + +echo "Lithuanian:" +tx pull -f -l lt + +echo "Persian:" +tx pull -f -l fa + +echo "Persian (Iran) فارسی/پارسی (ایران‎):" +tx pull -f -l fa_IR echo "Indonesian (Indonesia):" tx pull -f -l id_ID @@ -96,15 +114,22 @@ tx pull -f -l mk echo "Mongolian (Mongolia):" tx pull -f -l mn_MN -echo "Dutch:" -tx pull -f -l nl +#echo "Norwegian:" +#tx pull -f -l no +# This is same as no below -echo "Norwegian:" -tx pull -f -l no +echo "Norwegian Bokmål:" +tx pull -f -l nb -echo "Occitan:" +echo "Occitan (post 1500):" tx pull -f -l oc +echo "Persian:" +tx pull -f -l fa + +echo "Panjabi (Punjabi):" +tx pull -f -l pa + echo "Polish:" tx pull -f -l pl @@ -120,9 +145,36 @@ tx pull -f -l ro echo "Russian:" tx pull -f -l ru +echo "Spanish:" +tx pull -f -l es + +echo "Spanish (Argentina):" +tx pull -f -l es_AR + +echo "Spanish (Chile):" +tx pull -f -l es_CL + +echo "Spanish (Latin America)": +tx pull -f -l es_419 + +echo "Spanish (Mexico)": +tx pull -f -l es_MX + +echo "Spanish (Paraguay):" +tx pull -f -l es_PY + +echo "Spanish (Peru):" +tx pull -f -l es_PE + echo "Serbian:" tx pull -f -l sr +echo "Slovak:" +tx pull -f -l sk + +echo "Slovenian:" +tx pull -f -l sl_SI + echo "Swahili:" tx pull -f -l sw @@ -143,12 +195,3 @@ tx pull -f -l uk echo "Vietnamese:" tx pull -f -l vi - -echo "Chinese (China):" -tx pull -f -l zh_CN - -echo "Chinese (Taiwan)" -tx pull -f -l zh_TW - -echo "Chinese (Hong Kong)" -tx pull -f -l zh_HK diff --git a/releases/up-a.sh b/releases/up-a.sh new file mode 100755 index 000000000..7eebc9270 --- /dev/null +++ b/releases/up-a.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# 1) Check that there is only one parameter +# of Wekan version number + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./up-a.sh 5.10" + exit 1 +fi + +# 2) Download release from arm64 build server +scp a:/home/wekan/repos/wekan-$1-arm64.zip . + +# 3) Upload arm64 release to download server +scp wekan-$1-arm64.zip x2:/var/snap/wekan/common/releases.wekan.team/raspi3/ diff --git a/releases/up-o.sh b/releases/up-o.sh new file mode 100755 index 000000000..6fe939073 --- /dev/null +++ b/releases/up-o.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# 1) Check that there is only one parameter +# of Wekan version number + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./up-o.sh 5.10" + exit 1 +fi + +# 2) Download release from ppc64le build server +scp o:/home/ubuntu/wekan-$1-ppc64le.zip . + +# 3) Upload ppc64le release to download server +scp wekan-$1-ppc64le.zip x2:/var/snap/wekan/common/releases.wekan.team/ppc64le/ diff --git a/releases/up-s.sh b/releases/up-s.sh new file mode 100755 index 000000000..37c47c77c --- /dev/null +++ b/releases/up-s.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# 1) Check that there is only one parameter +# of Wekan version number + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./up-s.sh 5.10" + exit 1 +fi + +# 2) Download release from s390x build server +scp s:/home/linux1/wekan-$1-s390x.zip . + +# 3) Upload s390x release to download server +scp wekan-$1-s390x.zip x2:/var/snap/wekan/common/releases.wekan.team/s390x/ diff --git a/releases/up.sh b/releases/up.sh new file mode 100755 index 000000000..357e29cf2 --- /dev/null +++ b/releases/up.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# 1) Check that there is only one parameter +# of Wekan version number + +if [ $# -ne 1 ] + then + echo "Syntax with Wekan version number:" + echo " ./maintainer-make-bundle-o.sh 5.10" + exit 1 +fi + +# 2) Install parallel if it's not installed yet +sudo apt-get -y install parallel + +# 3) Download releases from build servers and +# upload releases to download server, +# all at the same time in parallel. + +{ + ~/repos/wekan/releases/up-a.sh $1 + ~/repos/wekan/releases/up-s.sh $1 + ~/repos/wekan/releases/up-o.sh $1 +} | parallel -k diff --git a/releases/virtualbox/start-wekan.sh b/releases/virtualbox/start-wekan.sh index 8d1f48e63..9ee48e671 100755 --- a/releases/virtualbox/start-wekan.sh +++ b/releases/virtualbox/start-wekan.sh @@ -1,351 +1,391 @@ # If you want to restart even on crash, uncomment while and done lines. #while true; do - cd ~/repos/wekan/.build/bundle - #--------------------------------------------- - # Debug OIDC OAuth2 etc. - #export export DEBUG=true - #--------------------------------------------- - export MONGO_URL='mongodb://127.0.0.1:27017/admin' - # ROOT_URL EXAMPLES FOR WEBSERVERS: https://github.com/wekan/wekan/wiki/Settings - # Production: https://example.com/wekan - # Local: http://localhost:3000 - #export ipaddress=$(ifdata -pa eth0) - export ROOT_URL='http://localhost' - #--------------------------------------------- - # Working email IS NOT REQUIRED to use Wekan. - # https://github.com/wekan/wekan/wiki/Adding-users - # https://github.com/wekan/wekan/wiki/Troubleshooting-Mail - # https://github.com/wekan/wekan-mongodb/blob/master/docker-compose.yml - export MAIL_URL='smtp://user:pass@mailserver.example.com:25/' - export MAIL_FROM='Wekan Support ' - # This is local port where Wekan Node.js runs, same as below on Caddyfile settings. - export PORT=80 - #--------------------------------------------- - # Wekan Export Board works when WITH_API='true'. - # If you disable Wekan API, Export Board does not work. - export WITH_API='true' - #--------------------------------------------------------------- - # ==== PASSWORD BRUTE FORCE PROTECTION ==== - #https://atmospherejs.com/lucasantoniassi/accounts-lockout - #Defaults below. Uncomment to change. wekan/server/accounts-lockout.js - #export ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 - #export ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 - #export ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 - #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 - #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 - #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 - #--------------------------------------------------------------- - # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== - # https://github.com/wekan/wekan/pull/2560 - export RICHER_CARD_COMMENT_EDITOR=true - #--------------------------------------------------------------- - # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== - export CARD_OPENED_WEBHOOK_ENABLED=false - #--------------------------------------------------------------- - # ==== Allow to shrink attached/pasted image ==== - # https://github.com/wekan/wekan/pull/2544 - #export MAX_IMAGE_PIXEL=1024 - #export IMAGE_COMPRESS_RATIO=80 - #--------------------------------------------------------------- - # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== - # https://github.com/wekan/wekan/pull/2541 - # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", - # so any activityType matches the pattern, system will send out - # notifications to all board members no matter they are watching - # or tracking the board or not. Owner of the wekan server can - # disable the feature by setting this variable to "NONE" or - # change the pattern to any valid regex. i.e. '|' delimited - # activityType names. - # a) Example - #export BIGEVENTS_PATTERN=due - # b) All - #export BIGEVENTS_PATTERN=received|start|due|end - # c) Disabled - export BIGEVENTS_PATTERN=NONE - #--------------------------------------------------------------- - # ==== EMAIL DUE DATE NOTIFICATION ===== - # https://github.com/wekan/wekan/pull/2536 - # System timelines will be showing any user modification for - # dueat startat endat receivedat, also notification to - # the watchers and if any card is due, about due or past due. - # - # Notify due days, default is None. - #export NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0 - # it will notify user 2 days before due day and on the due day - # - # Notify due at hour of day. Default every morning at 8am. Can be 0-23. - # If env variable has parsing error, use default. Notification sent to watchers. - #export NOTIFY_DUE_AT_HOUR_OF_DAY=8 - #----------------------------------------------------------------- - # ==== EMAIL NOTIFICATION TIMEOUT, ms ===== - # Defaut: 30000 ms = 30s - #export EMAIL_NOTIFICATION_TIMEOUT=30000 - #----------------------------------------------------------------- - # CORS: Set Access-Control-Allow-Origin header. Example: * - #export CORS=* - # To enable the Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API. - #export CORS_ALLOW_HEADERS=Authorization,Content-Type - # To enable the Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations. Example: * - #export CORS_EXPOSE_HEADERS=* - #--------------------------------------------- - ## Optional: Integration with Matomo https://matomo.org that is installed to your server - ## The address of the server where Matomo is hosted: - ##export MATOMO_ADDRESS=https://example.com/matomo - #export MATOMO_ADDRESS= - ## The value of the site ID given in Matomo server for Wekan - # Example: export MATOMO_SITE_ID=123456789 - #export MATOMO_SITE_ID='' - ## The option do not track which enables users to not be tracked by matomo" - #Example: export MATOMO_DO_NOT_TRACK=false - #export MATOMO_DO_NOT_TRACK=true - ## The option that allows matomo to retrieve the username: - # Example: export MATOMO_WITH_USERNAME=true - #export MATOMO_WITH_USERNAME='false' - # Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside. - # Setting this to false is not recommended, it also disables all other browser policy protections - # and allows all iframing etc. See wekan/server/policy.js - # Default value: true - export BROWSER_POLICY_ENABLED=true - # When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside. - # Example: export TRUSTED_URL=http://example.com - export TRUSTED_URL='' - # What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId . - # Example: export WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId - export WEBHOOKS_ATTRIBUTES='' - #--------------------------------------------- - # ==== OAUTH2 AZURE ==== - # https://github.com/wekan/wekan/wiki/Azure - # 1) Register the application with Azure. Make sure you capture - # the application ID as well as generate a secret key. - # 2) Configure the environment variables. This differs slightly - # by installation type, but make sure you have the following: - #export OAUTH2_ENABLED=true - # OAuth2 login style: popup or redirect. - #export OAUTH2_LOGIN_STYLE=redirect - # Application GUID captured during app registration: - #export OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx - # Secret key generated during app registration: - #export OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - #export OAUTH2_SERVER_URL=https://login.microsoftonline.com/ - #export OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize - #export OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo - #export OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token - # OAUTH2 ID Token Whitelist Fields. - #export OAUTH2_ID_TOKEN_WHITELIST_FIELDS=[] - # OAUTH2 Request Permissions. - #export OAUTH2_REQUEST_PERMISSIONS='openid profile email' - # The claim name you want to map to the unique ID field: - #export OAUTH2_ID_MAP=email - # The claim name you want to map to the username field: - #export OAUTH2_USERNAME_MAP=email - # The claim name you want to map to the full name field: - #export OAUTH2_FULLNAME_MAP=name - # Tthe claim name you want to map to the email field: - #export OAUTH2_EMAIL_MAP=email - #----------------------------------------------------------------- - # ==== OAUTH2 KEYCLOAK ==== - # https://github.com/wekan/wekan/wiki/Keycloak <== MAPPING INFO, REQUIRED - #export OAUTH2_ENABLED=true - # OAuth2 login style: popup or redirect. - #export OAUTH2_LOGIN_STYLE=redirect - #export OAUTH2_CLIENT_ID= - #export OAUTH2_SERVER_URL=/auth - #export OAUTH2_AUTH_ENDPOINT=/realms//protocol/openid-connect/auth - #export OAUTH2_USERINFO_ENDPOINT=/realms//protocol/openid-connect/userinfo - #export OAUTH2_TOKEN_ENDPOINT=/realms//protocol/openid-connect/token - #export OAUTH2_SECRET= - #----------------------------------------------------------------- - # ==== OAUTH2 DOORKEEPER ==== - # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 - # https://github.com/wekan/wekan/issues/1874 - # https://github.com/wekan/wekan/wiki/OAuth2 - # Enable the OAuth2 connection - #export OAUTH2_ENABLED=true - # OAuth2 login style: popup or redirect. - #export OAUTH2_LOGIN_STYLE=redirect - # OAuth2 Client ID. - #export OAUTH2_CLIENT_ID=abcde12345 - # OAuth2 Secret. - #export OAUTH2_SECRET=54321abcde - # OAuth2 Server URL. - #export OAUTH2_SERVER_URL=https://chat.example.com - # OAuth2 Authorization Endpoint. - #export OAUTH2_AUTH_ENDPOINT=/oauth/authorize - # OAuth2 Userinfo Endpoint. - #export OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo - # OAuth2 Token Endpoint. - #export OAUTH2_TOKEN_ENDPOINT=/oauth/token - # OAuth2 ID Mapping - #export OAUTH2_ID_MAP= - # OAuth2 Username Mapping - #export OAUTH2_USERNAME_MAP= - # OAuth2 Fullname Mapping - #export OAUTH2_FULLNAME_MAP= - # OAuth2 Email Mapping - #export OAUTH2_EMAIL_MAP= - #--------------------------------------------- - # LDAP_ENABLE : Enable or not the connection by the LDAP - # example : export LDAP_ENABLE=true - #export LDAP_ENABLE=false - # LDAP_PORT : The port of the LDAP server - # example : export LDAP_PORT=389 - #export LDAP_PORT=389 - # LDAP_HOST : The host server for the LDAP server - # example : export LDAP_HOST=localhost - #export LDAP_HOST= - # LDAP_BASEDN : The base DN for the LDAP Tree - # example : export LDAP_BASEDN=ou=user,dc=example,dc=org - #export LDAP_BASEDN= - # LDAP_LOGIN_FALLBACK : Fallback on the default authentication method - # example : export LDAP_LOGIN_FALLBACK=true - #export LDAP_LOGIN_FALLBACK=false - # LDAP_RECONNECT : Reconnect to the server if the connection is lost - # example : export LDAP_RECONNECT=false - #export LDAP_RECONNECT=true - # LDAP_TIMEOUT : Overall timeout, in milliseconds - # example : export LDAP_TIMEOUT=12345 - #export LDAP_TIMEOUT=10000 - # LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds - # example : export LDAP_IDLE_TIMEOUT=12345 - #export LDAP_IDLE_TIMEOUT=10000 - # LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds - # example : export LDAP_CONNECT_TIMEOUT=12345 - #export LDAP_CONNECT_TIMEOUT=10000 - # LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search - # example : export LDAP_AUTHENTIFICATION=true - #export LDAP_AUTHENTIFICATION=false - # LDAP_AUTHENTIFICATION_USERDN : The search user DN - # example : export LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org - #export LDAP_AUTHENTIFICATION_USERDN= - # LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user - # example : AUTHENTIFICATION_PASSWORD=admin - #export LDAP_AUTHENTIFICATION_PASSWORD= - # LDAP_LOG_ENABLED : Enable logs for the module - # example : export LDAP_LOG_ENABLED=true - #export LDAP_LOG_ENABLED=false - # LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background - # example : export LDAP_BACKGROUND_SYNC=true - #export LDAP_BACKGROUND_SYNC=false - # LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds - # At which interval does the background task sync in milliseconds. - # Leave this unset, so it uses default, and does not crash. - # https://github.com/wekan/wekan/issues/2354#issuecomment-515305722 - export LDAP_BACKGROUND_SYNC_INTERVAL='' - # LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED : - # example : export LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true - #export LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false - # LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS : - # example : export LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true - #export LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false - # LDAP_ENCRYPTION : If using LDAPS - # example : export LDAP_ENCRYPTION=ssl - #export LDAP_ENCRYPTION=false - # LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file. - # example : export LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE----- - #export LDAP_CA_CERT= - # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate - # example : export LDAP_REJECT_UNAUTHORIZED=true - #export LDAP_REJECT_UNAUTHORIZED=false - # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key). - #export LDAP_USER_AUTHENTICATION=true - # Which field is used to find the user for the user authentication. Default: uid. - #export LDAP_USER_AUTHENTICATION_FIELD=uid - # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed - # example : export LDAP_USER_SEARCH_FILTER= - #export LDAP_USER_SEARCH_FILTER= - # LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree) - # example : export LDAP_USER_SEARCH_SCOPE=one - #export LDAP_USER_SEARCH_SCOPE= - # LDAP_USER_SEARCH_FIELD : Which field is used to find the user - # example : export LDAP_USER_SEARCH_FIELD=uid - #export LDAP_USER_SEARCH_FIELD= - # LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited) - # example : export LDAP_SEARCH_PAGE_SIZE=12345 - #export LDAP_SEARCH_PAGE_SIZE=0 - # LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited) - # example : export LDAP_SEARCH_SIZE_LIMIT=12345 - #export LDAP_SEARCH_SIZE_LIMIT=0 - # LDAP_GROUP_FILTER_ENABLE : Enable group filtering - # example : export LDAP_GROUP_FILTER_ENABLE=true - #export LDAP_GROUP_FILTER_ENABLE=false - # LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering - # example : export LDAP_GROUP_FILTER_OBJECTCLASS=group - #export LDAP_GROUP_FILTER_OBJECTCLASS= - # LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE : - # example : - #export LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE= - # LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE : - # example : - #export LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE= - # LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT : - # example : - #export LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT= - # LDAP_GROUP_FILTER_GROUP_NAME : - # example : - #export LDAP_GROUP_FILTER_GROUP_NAME= - # LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier) - # example : export LDAP_UNIQUE_IDENTIFIER_FIELD=guid - #export LDAP_UNIQUE_IDENTIFIER_FIELD= - # LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8 - # example : export LDAP_UTF8_NAMES_SLUGIFY=false - #export LDAP_UTF8_NAMES_SLUGIFY=true - # LDAP_USERNAME_FIELD : Which field contains the ldap username - # example : export LDAP_USERNAME_FIELD=username - #export LDAP_USERNAME_FIELD= - # LDAP_FULLNAME_FIELD : Which field contains the ldap fullname - # example : export LDAP_FULLNAME_FIELD=fullname - #export LDAP_FULLNAME_FIELD= - # LDAP_MERGE_EXISTING_USERS : - # example : export LDAP_MERGE_EXISTING_USERS=true - #export LDAP_MERGE_EXISTING_USERS=false - # LDAP_EMAIL_MATCH_ENABLE : allow existing account matching by e-mail address when username does not match - # example: LDAP_EMAIL_MATCH_ENABLE=true - #export LDAP_EMAIL_MATCH_ENABLE=false - # LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match - # example: LDAP_EMAIL_MATCH_REQUIRE=true - #export LDAP_EMAIL_MATCH_REQUIRE=false - # LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching - # example: LDAP_EMAIL_MATCH_VERIFIED=true - #export LDAP_EMAIL_MATCH_VERIFIED=false - # LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address - # example: LDAP_EMAIL_FIELD=mail - #export LDAP_EMAIL_FIELD= - # LDAP_SYNC_USER_DATA : - # example : export LDAP_SYNC_USER_DATA=true - #export LDAP_SYNC_USER_DATA=false - # LDAP_SYNC_USER_DATA_FIELDMAP : - # example : export LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"} - #export LDAP_SYNC_USER_DATA_FIELDMAP= - # LDAP_SYNC_GROUP_ROLES : - # example : - #export LDAP_SYNC_GROUP_ROLES= - # LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP - # example : - #export LDAP_DEFAULT_DOMAIN= - # Enable/Disable syncing of admin status based on ldap groups: - #export LDAP_SYNC_ADMIN_STATUS=true - # Comma separated list of admin group names. - #export LDAP_SYNC_ADMIN_GROUPS=group1,group2 - #--------------------------------------------------------------------- - # Login to LDAP automatically with HTTP header. - # In below example for siteminder, at right side of = is header name. - #export HEADER_LOGIN_ID=HEADERUID - #export HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME - #export HEADER_LOGIN_LASTNAME=HEADERLASTNAME - #export HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS - #--------------------------------------------------------------------- - # LOGOUT_WITH_TIMER : Enables or not the option logout with timer - # example : LOGOUT_WITH_TIMER=true - #export LOGOUT_WITH_TIMER= - # LOGOUT_IN : The number of days - # example : LOGOUT_IN=1 - #export LOGOUT_IN= - #export LOGOUT_ON_HOURS= - # LOGOUT_ON_MINUTES : The number of minutes - # example : LOGOUT_ON_MINUTES=55 - #export LOGOUT_ON_MINUTES= + cd ~/repos/wekan/.build/bundle + #--------------------------------------------- + # Debug OIDC OAuth2 etc. + #export DEBUG=true + #--------------------------------------------- + export MONGO_URL='mongodb://127.0.0.1:27017/wekan' + #--------------------------------------------- + # Production: https://example.com/wekan + # Local: http://localhost:2000 + #export ipaddress=$(ifdata -pa eth0) + export ROOT_URL='http://localhost:2000' + #--------------------------------------------- + # https://github.com/wekan/wekan/wiki/Troubleshooting-Mail + # https://github.com/wekan/wekan-mongodb/blob/master/docker-compose.yml + export MAIL_URL='smtp://user:pass@mailserver.example.com:25/' + #--------------------------------------------- + #export KADIRA_OPTIONS_ENDPOINT=http://127.0.0.1:11011 + #--------------------------------------------- + # This is local port where Wekan Node.js runs, same as below on Caddyfile settings. + export PORT=2000 + #--------------------------------------------- + # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== + #export RESULTS_PER_PAGE=20 + #--------------------------------------------- + # Wekan Export Board works when WITH_API=true. + # If you disable Wekan API with false, Export Board does not work. + export WITH_API='true' + #--------------------------------------------------------------- + # ==== PASSWORD BRUTE FORCE PROTECTION ==== + #https://atmospherejs.com/lucasantoniassi/accounts-lockout + #Defaults below. Uncomment to change. wekan/server/accounts-lockout.js + #export ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 + #export ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 + #export ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 + #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 + #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 + #export ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 + #--------------------------------------------------------------- + # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== + # https://github.com/wekan/wekan/pull/2560 + export RICHER_CARD_COMMENT_EDITOR=false + #--------------------------------------------------------------- + # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== + export CARD_OPENED_WEBHOOK_ENABLED=false + #--------------------------------------------------------------- + # ==== Allow to shrink attached/pasted image ==== + # https://github.com/wekan/wekan/pull/2544 + #export MAX_IMAGE_PIXEL=1024 + #export IMAGE_COMPRESS_RATIO=80 + #--------------------------------------------------------------- + # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== + # Number of days after a notification is read before we remove it. + # Default: 2 + #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + #--------------------------------------------------------------- + # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== + # https://github.com/wekan/wekan/pull/2541 + # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", + # so any activityType matches the pattern, system will send out + # notifications to all board members no matter they are watching + # or tracking the board or not. Owner of the wekan server can + # disable the feature by setting this variable to "NONE" or + # change the pattern to any valid regex. i.e. '|' delimited + # activityType names. + # a) Example + #export BIGEVENTS_PATTERN=due + # b) All + #export BIGEVENTS_PATTERN=received|start|due|end + # c) Disabled + export BIGEVENTS_PATTERN=NONE + #--------------------------------------------------------------- + # ==== EMAIL DUE DATE NOTIFICATION ===== + # https://github.com/wekan/wekan/pull/2536 + # System timelines will be showing any user modification for + # dueat startat endat receivedat, also notification to + # the watchers and if any card is due, about due or past due. + # + # Notify due days, default is None. + #export NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0 + # it will notify user 2 days before due day and on the due day + # + # Notify due at hour of day. Default every morning at 8am. Can be 0-23. + # If env variable has parsing error, use default. Notification sent to watchers. + #export NOTIFY_DUE_AT_HOUR_OF_DAY=8 + #----------------------------------------------------------------- + # ==== EMAIL NOTIFICATION TIMEOUT, ms ===== + # Defaut: 30000 ms = 30s + #export EMAIL_NOTIFICATION_TIMEOUT=30000 + #----------------------------------------------------------------- + # CORS: Set Access-Control-Allow-Origin header. Example: * + #export CORS=* + # To enable the Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API. + #export CORS_ALLOW_HEADERS=Authorization,Content-Type + # To enable the Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations. Example: * + #export CORS_EXPOSE_HEADERS=* + #--------------------------------------------- + ## Optional: Integration with Matomo https://matomo.org that is installed to your server + ## The address of the server where Matomo is hosted: + ##export MATOMO_ADDRESS=https://example.com/matomo + #export MATOMO_ADDRESS= + ## The value of the site ID given in Matomo server for Wekan + # Example: export MATOMO_SITE_ID=123456789 + #export MATOMO_SITE_ID='' + ## The option do not track which enables users to not be tracked by matomo" + #Example: export MATOMO_DO_NOT_TRACK=false + #export MATOMO_DO_NOT_TRACK=true + ## The option that allows matomo to retrieve the username: + # Example: export MATOMO_WITH_USERNAME=true + #export MATOMO_WITH_USERNAME='false' + # Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside. + # Setting this to false is not recommended, it also disables all other browser policy protections + # and allows all iframing etc. See wekan/server/policy.js + # Default value: true + export BROWSER_POLICY_ENABLED=true + # When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside. + # Example: export TRUSTED_URL=http://example.com + export TRUSTED_URL='' + # What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId . + # Example: export WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId + export WEBHOOKS_ATTRIBUTES='' + #--------------------------------------------- + # ==== OAUTH2 AZURE ==== + # https://github.com/wekan/wekan/wiki/Azure + # 1) Register the application with Azure. Make sure you capture + # the application ID as well as generate a secret key. + # 2) Configure the environment variables. This differs slightly + # by installation type, but make sure you have the following: + #export OAUTH2_ENABLED=true + # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. + #export OAUTH2_ADFS_ENABLED=false + # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 + # OAuth2 login style: popup or redirect. + #export OAUTH2_LOGIN_STYLE=redirect + # Application GUID captured during app registration: + #export OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx + # Secret key generated during app registration: + #export OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + #export OAUTH2_SERVER_URL=https://login.microsoftonline.com/ + #export OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize + #export OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo + #export OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token + # The claim name you want to map to the unique ID field: + #export OAUTH2_ID_MAP=email + # The claim name you want to map to the username field: + #export OAUTH2_USERNAME_MAP=email + # The claim name you want to map to the full name field: + #export OAUTH2_FULLNAME_MAP=name + # The claim name you want to map to the email field: + #export OAUTH2_EMAIL_MAP=email + #----------------------------------------------------------------- + # ==== OAUTH2 KEYCLOAK ==== + # https://github.com/wekan/wekan/wiki/Keycloak <== MAPPING INFO, REQUIRED + #export OAUTH2_ENABLED=true + # OAuth2 login style: popup or redirect. + #export OAUTH2_LOGIN_STYLE=redirect + #export OAUTH2_CLIENT_ID= + #export OAUTH2_SERVER_URL=/auth + #export OAUTH2_AUTH_ENDPOINT=/realms//protocol/openid-connect/auth + #export OAUTH2_USERINFO_ENDPOINT=/realms//protocol/openid-connect/userinfo + #export OAUTH2_TOKEN_ENDPOINT=/realms//protocol/openid-connect/token + #export OAUTH2_SECRET= + #----------------------------------------------------------------- + # ==== OAUTH2 DOORKEEPER ==== + # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 + # https://github.com/wekan/wekan/issues/1874 + # https://github.com/wekan/wekan/wiki/OAuth2 + # Enable the OAuth2 connection + #export OAUTH2_ENABLED=true + # OAuth2 login style: popup or redirect. + #export OAUTH2_LOGIN_STYLE=redirect + # OAuth2 Client ID. + #export OAUTH2_CLIENT_ID=abcde12345 + # OAuth2 Secret. + #export OAUTH2_SECRET=54321abcde + # OAuth2 Server URL. + #export OAUTH2_SERVER_URL=https://chat.example.com + # OAuth2 Authorization Endpoint. + #export OAUTH2_AUTH_ENDPOINT=/oauth/authorize + # OAuth2 Userinfo Endpoint. + #export OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo + # OAuth2 Token Endpoint. + #export OAUTH2_TOKEN_ENDPOINT=/oauth/token + # OAUTH2 ID Token Whitelist Fields. + #export OAUTH2_ID_TOKEN_WHITELIST_FIELDS=[] + # OAUTH2 Request Permissions. + #export OAUTH2_REQUEST_PERMISSIONS='openid profile email' + # OAuth2 ID Mapping + #export OAUTH2_ID_MAP= + # OAuth2 Username Mapping + #export OAUTH2_USERNAME_MAP= + # OAuth2 Fullname Mapping + #export OAUTH2_FULLNAME_MAP= + # OAuth2 Email Mapping + #export OAUTH2_EMAIL_MAP= + #--------------------------------------------- + # LDAP_ENABLE : Enable or not the connection by the LDAP + # example : export LDAP_ENABLE=true + #export LDAP_ENABLE=false + # LDAP_PORT : The port of the LDAP server + # example : export LDAP_PORT=389 + #export LDAP_PORT=389 + # LDAP_HOST : The host server for the LDAP server + # example : export LDAP_HOST=localhost + #export LDAP_HOST= + # LDAP_BASEDN : The base DN for the LDAP Tree + # example : export LDAP_BASEDN=ou=user,dc=example,dc=org + #export LDAP_BASEDN= + # LDAP_LOGIN_FALLBACK : Fallback on the default authentication method + # example : export LDAP_LOGIN_FALLBACK=true + #export LDAP_LOGIN_FALLBACK=false + # LDAP_RECONNECT : Reconnect to the server if the connection is lost + # example : export LDAP_RECONNECT=false + #export LDAP_RECONNECT=true + # LDAP_TIMEOUT : Overall timeout, in milliseconds + # example : export LDAP_TIMEOUT=12345 + #export LDAP_TIMEOUT=10000 + # LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds + # example : export LDAP_IDLE_TIMEOUT=12345 + #export LDAP_IDLE_TIMEOUT=10000 + # LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds + # example : export LDAP_CONNECT_TIMEOUT=12345 + #export LDAP_CONNECT_TIMEOUT=10000 + # LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search + # example : export LDAP_AUTHENTIFICATION=true + #export LDAP_AUTHENTIFICATION=false + # LDAP_AUTHENTIFICATION_USERDN : The search user DN + # example : export LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org + #---------------------------------------------------------------------------- + # The search user DN - You need quotes when you have spaces in parameters + # 2 examples: + #export LDAP_AUTHENTIFICATION_USERDN="CN=ldap admin,CN=users,DC=domainmatter,DC=lan" + #export LDAP_AUTHENTIFICATION_USERDN="CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com" + #--------------------------------------------------------------------------- + # LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user + # example : AUTHENTIFICATION_PASSWORD=admin + #export LDAP_AUTHENTIFICATION_PASSWORD= + # LDAP_LOG_ENABLED : Enable logs for the module + # example : export LDAP_LOG_ENABLED=true + #export LDAP_LOG_ENABLED=false + # LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background + # example : export LDAP_BACKGROUND_SYNC=true + #export LDAP_BACKGROUND_SYNC=false + # LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds + # At which interval does the background task sync in milliseconds. + # Leave this unset, so it uses default, and does not crash. + # https://github.com/wekan/wekan/issues/2354#issuecomment-515305722 + export LDAP_BACKGROUND_SYNC_INTERVAL='' + # LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED : + # example : export LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true + #export LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false + # LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS : + # example : export LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true + #export LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false + # LDAP_ENCRYPTION : If using LDAPS + # example : export LDAP_ENCRYPTION=ssl + #export LDAP_ENCRYPTION=false + # LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file. + # example : export LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE----- + #export LDAP_CA_CERT= + # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate + # example : export LDAP_REJECT_UNAUTHORIZED=true + #export LDAP_REJECT_UNAUTHORIZED=false + # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key). + #export LDAP_USER_AUTHENTICATION=true + # Which field is used to find the user for the user authentication. Default: uid. + #export LDAP_USER_AUTHENTICATION_FIELD=uid + # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed + # example : export LDAP_USER_SEARCH_FILTER= + #export LDAP_USER_SEARCH_FILTER= + # LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree) + # example : export LDAP_USER_SEARCH_SCOPE=one + #export LDAP_USER_SEARCH_SCOPE= + # LDAP_USER_SEARCH_FIELD : Which field is used to find the user + # example : export LDAP_USER_SEARCH_FIELD=uid + #export LDAP_USER_SEARCH_FIELD= + # LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited) + # example : export LDAP_SEARCH_PAGE_SIZE=12345 + #export LDAP_SEARCH_PAGE_SIZE=0 + # LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited) + # example : export LDAP_SEARCH_SIZE_LIMIT=12345 + #export LDAP_SEARCH_SIZE_LIMIT=0 + # LDAP_GROUP_FILTER_ENABLE : Enable group filtering + # example : export LDAP_GROUP_FILTER_ENABLE=true + #export LDAP_GROUP_FILTER_ENABLE=false + # LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering + # example : export LDAP_GROUP_FILTER_OBJECTCLASS=group + #export LDAP_GROUP_FILTER_OBJECTCLASS= + # LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE : + # example : + #export LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE= + # LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE : + # example : + #export LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE= + # LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT : + # example : + #export LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT= + # LDAP_GROUP_FILTER_GROUP_NAME : + # example : + #export LDAP_GROUP_FILTER_GROUP_NAME= + # LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier) + # example : export LDAP_UNIQUE_IDENTIFIER_FIELD=guid + #export LDAP_UNIQUE_IDENTIFIER_FIELD= + # LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8 + # example : export LDAP_UTF8_NAMES_SLUGIFY=false + #export LDAP_UTF8_NAMES_SLUGIFY=true + # LDAP_USERNAME_FIELD : Which field contains the ldap username + # example : export LDAP_USERNAME_FIELD=username + #export LDAP_USERNAME_FIELD= + # LDAP_FULLNAME_FIELD : Which field contains the ldap fullname + # example : export LDAP_FULLNAME_FIELD=fullname + #export LDAP_FULLNAME_FIELD= + # LDAP_MERGE_EXISTING_USERS : + # example : export LDAP_MERGE_EXISTING_USERS=true + #export LDAP_MERGE_EXISTING_USERS=false + # LDAP_EMAIL_MATCH_ENABLE : allow existing account matching by e-mail address when username does not match + # example: LDAP_EMAIL_MATCH_ENABLE=true + #export LDAP_EMAIL_MATCH_ENABLE=false + # LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match + # example: LDAP_EMAIL_MATCH_REQUIRE=true + #export LDAP_EMAIL_MATCH_REQUIRE=false + # LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching + # example: LDAP_EMAIL_MATCH_VERIFIED=true + #export LDAP_EMAIL_MATCH_VERIFIED=false + # LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address + # example: LDAP_EMAIL_FIELD=mail + #export LDAP_EMAIL_FIELD= + # LDAP_SYNC_USER_DATA : + # example : export LDAP_SYNC_USER_DATA=true + #export LDAP_SYNC_USER_DATA=false + # LDAP_SYNC_USER_DATA_FIELDMAP : + # example : export LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"} + #export LDAP_SYNC_USER_DATA_FIELDMAP= + # LDAP_SYNC_GROUP_ROLES : + # example : + #export LDAP_SYNC_GROUP_ROLES= + # LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP + # example : + #export LDAP_DEFAULT_DOMAIN= + # Enable/Disable syncing of admin status based on ldap groups: + #export LDAP_SYNC_ADMIN_STATUS=true + # Comma separated list of admin group names to sync. + #export LDAP_SYNC_ADMIN_GROUPS=group1,group2 + #--------------------------------------------------------------------- + # Login to LDAP automatically with HTTP header. + # In below example for siteminder, at right side of = is header name. + #export HEADER_LOGIN_ID=HEADERUID + #export HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME + #export HEADER_LOGIN_LASTNAME=HEADERLASTNAME + #export HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS + #--------------------------------------------------------------------- + # LOGOUT_WITH_TIMER : Enables or not the option logout with timer + # example : LOGOUT_WITH_TIMER=true + #export LOGOUT_WITH_TIMER= + # LOGOUT_IN : The number of days + # example : LOGOUT_IN=1 + #export LOGOUT_IN= + #export LOGOUT_ON_HOURS= + # LOGOUT_ON_MINUTES : The number of minutes + # example : LOGOUT_ON_MINUTES=55 + #export LOGOUT_ON_MINUTES= + #--------------------------------------------------------------------- + # PASSWORD_LOGIN_ENABLED : Enable or not the password login form. + #export PASSWORD_LOGIN_ENABLED=true + #--------------------------------------------------------------------- + #export CAS_ENABLED=true + #export CAS_BASE_URL=https://cas.example.com/cas + #export CAS_LOGIN_URL=https://cas.example.com/login + #export CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + #--------------------------------------------------------------------- + #export SAML_ENABLED=true + #export SAML_PROVIDER= + #export SAML_ENTRYPOINT= + #export SAML_ISSUER= + #export SAML_CERT= + #export SAML_IDPSLO_REDIRECTURL= + #export SAML_PRIVATE_KEYFILE= + #export SAML_PUBLIC_CERTFILE= + #export SAML_IDENTIFIER_FORMAT= + #export SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= + #export SAML_ATTRIBUTES= + #--------------------------------------------------------------------- + # Wait spinner to use + #export WAIT_SPINNER=Bounce + #--------------------------------------------------------------------- - node main.js & >> ~/repos/wekan.log - cd ~/repos + node main.js & >> ~/repos/wekan.log + cd ~/repos #done diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index 65edd1dde..21a369b46 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Wekan"), # The name of the app as it is displayed to the user. - appVersion = 390, + appVersion = 538, # Increment this for every release. - appMarketingVersion = (defaultText = "3.90.0~2020-04-06"), + appMarketingVersion = (defaultText = "5.38.0~2021-07-18"), # Human-readable presentation of the app version. minUpgradableAppVersion = 0, @@ -237,11 +237,11 @@ const myCommand :Spk.Manifest.Command = ( environ = [ # Note that this defines the *entire* environment seen by your app. (key = "PATH", value = "/usr/local/bin:/usr/bin:/bin"), + (key = "RESULTS_PER_PAGE", value = ""), (key = "WITH_API", value = "true"), (key = "RICHER_CARD_COMMENT_EDITOR", value="false"), - (key = "SCROLLINERTIA", value="0"), - (key = "SCROLLAMOUNT", value="auto"), (key = "CARD_OPENED_WEBHOOK_ENABLED", value="false"), + (key = "NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE", value=""), (key = "BIGEVENTS_PATTERN", value="NONE"), (key = "MATOMO_ADDRESS", value=""), (key = "MATOMO_SITE_ID", value=""), @@ -250,7 +250,9 @@ const myCommand :Spk.Manifest.Command = ( (key = "BROWSER_POLICY_ENABLED", value="true"), (key = "TRUSTED_URL", value=""), (key = "WEBHOOKS_ATTRIBUTES", value=""), - (key = "OAUTH2_ENABLED", value=""), + (key = "OAUTH2_ENABLED", value="false"), + (key = "OAUTH2_CA_CERT", value=""), + (key = "OAUTH2_ADFS_ENABLED", value="false"), (key = "OAUTH2_CLIENT_ID", value="false"), (key = "OAUTH2_SECRET", value=""), (key = "OAUTH2_SERVER_URL", value=""), @@ -258,6 +260,7 @@ const myCommand :Spk.Manifest.Command = ( (key = "OAUTH2_USERINFO_ENDPOINT", value=""), (key = "OAUTH2_TOKEN_ENDPOINT", value=""), (key = "LDAP_ENABLE", value="false"), + (key = "PASSWORD_LOGIN_ENABLED", value="true"), (key = "SANDSTORM", value="1"), (key = "METEOR_SETTINGS", value = "{\"public\": {\"sandstorm\": true}}") ] diff --git a/sandstorm.js b/sandstorm.js index 590a24ec3..de386d149 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -22,9 +22,7 @@ const sandstormBoard = { if (isSandstorm && Meteor.isServer) { const fs = require('fs'); - const pathParts = process.cwd().split('/'); - const path = pathParts.join('/'); - const Capnp = Npm.require(`${path}../../../node_modules/capnp.js`); + const Capnp = Npm.require('capnp'); const Package = Capnp.importSystem('sandstorm/package.capnp'); const Powerbox = Capnp.importSystem('sandstorm/powerbox.capnp'); const Identity = Capnp.importSystem('sandstorm/identity.capnp'); diff --git a/server/accounts-lockout.js b/server/accounts-lockout.js index 2eb0409bf..4b9854cfb 100644 --- a/server/accounts-lockout.js +++ b/server/accounts-lockout.js @@ -1,21 +1,26 @@ -// https://atmospherejs.com/lucasantoniassi/accounts-lockout -// server -import { AccountsLockout } from 'meteor/lucasantoniassi:accounts-lockout'; +Meteor.startup(() => { + // https://atmospherejs.com/lucasantoniassi/accounts-lockout + // server -new AccountsLockout({ - knownUsers: { - failuresBeforeLockout: - process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE || 3, - lockoutPeriod: process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD || 60, - failureWindow: - process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW || 15, - }, - unknownUsers: { - failuresBeforeLockout: - process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE || 3, - lockoutPeriod: - process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD || 60, - failureWindow: - process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW || 15, - }, -}).startup(); + if (Meteor.isServer) { + import { AccountsLockout } from 'meteor/wekan-accounts-lockout'; + + new AccountsLockout({ + knownUsers: { + failuresBeforeLockout: + process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE || 3, + lockoutPeriod: process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD || 60, + failureWindow: + process.env.ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW || 15, + }, + unknownUsers: { + failuresBeforeLockout: + process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE || 3, + lockoutPeriod: + process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD || 60, + failureWindow: + process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW || 15, + }, + }).startup(); + } +}); diff --git a/server/authentication.js b/server/authentication.js index 203272804..965f16695 100644 --- a/server/authentication.js +++ b/server/authentication.js @@ -63,7 +63,32 @@ Meteor.startup(() => { }; if (Meteor.isServer) { - if (process.env.OAUTH2_CLIENT_ID !== '') { + if ( + process.env.ORACLE_OIM_ENABLED === 'true' || + process.env.ORACLE_OIM_ENABLED === true + ) { + ServiceConfiguration.configurations.upsert( + // eslint-disable-line no-undef + { service: 'oidc' }, + { + $set: { + loginStyle: process.env.OAUTH2_LOGIN_STYLE, + clientId: process.env.OAUTH2_CLIENT_ID, + secret: process.env.OAUTH2_SECRET, + serverUrl: process.env.OAUTH2_SERVER_URL, + authorizationEndpoint: process.env.OAUTH2_AUTH_ENDPOINT, + userinfoEndpoint: process.env.OAUTH2_USERINFO_ENDPOINT, + tokenEndpoint: process.env.OAUTH2_TOKEN_ENDPOINT, + idTokenWhitelistFields: + process.env.OAUTH2_ID_TOKEN_WHITELIST_FIELDS || [], + requestPermissions: process.env.OAUTH2_REQUEST_PERMISSIONS, + }, + }, + ); + } else if ( + process.env.OAUTH2_ENABLED === 'true' || + process.env.OAUTH2_ENABLED === true + ) { ServiceConfiguration.configurations.upsert( // eslint-disable-line no-undef { service: 'oidc' }, @@ -84,6 +109,73 @@ Meteor.startup(() => { // OAUTH2_REQUEST_PERMISSIONS || 'openid profile email', }, ); + } else if ( + process.env.CAS_ENABLED === 'true' || + process.env.CAS_ENABLED === true + ) { + ServiceConfiguration.configurations.upsert( + // eslint-disable-line no-undef + { service: 'cas' }, + { + $set: { + baseUrl: process.env.CAS_BASE_URL, + loginUrl: process.env.CAS_LOGIN_URL, + serviceParam: 'service', + popupWidth: 810, + popupHeight: 610, + popup: true, + autoClose: true, + validateUrl: process.env.CASE_VALIDATE_URL, + casVersion: 3.0, + attributes: { + debug: process.env.DEBUG, + }, + }, + }, + ); + } else if ( + process.env.SAML_ENABLED === 'true' || + process.env.SAML_ENABLED === true + ) { + ServiceConfiguration.configurations.upsert( + // eslint-disable-line no-undef + { service: 'saml' }, + { + $set: { + provider: process.env.SAML_PROVIDER, + entryPoint: process.env.SAML_ENTRYPOINT, + issuer: process.env.SAML_ISSUER, + cert: process.env.SAML_CERT, + idpSLORedirectURL: process.env.SAML_IDPSLO_REDIRECTURL, + privateKeyFile: process.env.SAML_PRIVATE_KEYFILE, + publicCertFile: process.env.SAML_PUBLIC_CERTFILE, + identifierFormat: process.env.SAML_IDENTIFIER_FORMAT, + localProfileMatchAttribute: + process.env.SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE, + attributesSAML: process.env.SAML_ATTRIBUTES || [ + 'sn', + 'givenName', + 'mail', + ], + + /* + settings = {"saml":[{ + "provider":"openam", + "entryPoint":"https://openam.idp.io/openam/SSORedirect/metaAlias/zimt/idp", + "issuer": "https://sp.zimt.io/", //replace with url of your app + "cert":"MIICizCCAfQCCQCY8tKaMc0 LOTS OF FUNNY CHARS ==", + "idpSLORedirectURL": "http://openam.idp.io/openam/IDPSloRedirect/metaAlias/zimt/idp", + "privateKeyFile": "certs/mykey.pem", // path is relative to $METEOR-PROJECT/private + "publicCertFile": "certs/mycert.pem", // eg $METEOR-PROJECT/private/certs/mycert.pem + "dynamicProfile": true // set to true if we want to create a user in Meteor.users dynamically if SAML assertion is valid + "identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", // Defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + "localProfileMatchAttribute": "telephoneNumber" // CAUTION: this will be mapped to profile. attribute in Mongo if identifierFormat (see above) differs from urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress, + "attributesSAML": [telephoneNumber, sn, givenName, mail], // attrs from SAML attr statement, which will be used for local Meteor profile creation. Currently no real attribute mapping. If required use mapping on IdP side. + }]} + */ + }, + }, + ); } } }); diff --git a/server/lib/tests/index.js b/server/lib/tests/index.js new file mode 100644 index 000000000..bcc3f567c --- /dev/null +++ b/server/lib/tests/index.js @@ -0,0 +1 @@ +import './utils.tests'; diff --git a/server/lib/tests/utils.tests.js b/server/lib/tests/utils.tests.js new file mode 100644 index 000000000..7abc946a8 --- /dev/null +++ b/server/lib/tests/utils.tests.js @@ -0,0 +1,106 @@ +/* eslint-env mocha */ +import { Random } from 'meteor/random'; +import { expect } from 'chai'; +import '../utils'; + +describe('utils', function() { + describe(allowIsBoardAdmin.name, function() { + it('returns if a board has an admin', function() { + const userId = Random.id(); + const board = { + hasAdmin: id => { + return id === userId; + } + }; + + expect(allowIsBoardAdmin(userId, board)).to.equal(true); + expect(allowIsBoardAdmin(Random.id(), board)).to.equal(false); + }); + }); + + describe(allowIsBoardMember.name, function() { + it('returns if a board has a member', function() { + const userId = Random.id(); + const board = { + hasMember: id => { + return id === userId; + } + }; + + expect(allowIsBoardMember(userId, board)).to.equal(true); + expect(allowIsBoardMember(Random.id(), board)).to.equal(false); + }); + }); + + describe(allowIsAnyBoardMember.name, function() { + it('returns if any board has a member', function() { + const userId = Random.id(); + const boardsExpectedTrue = [{ + hasMember: id => { + return id === userId; + } + }]; + + expect(allowIsAnyBoardMember(userId, boardsExpectedTrue)).to.equal(true); + expect(allowIsAnyBoardMember(Random.id(), boardsExpectedTrue)).to.equal(false); + + const boardsExpectedFalse = [{ + hasMember: () => false + }]; + + expect(allowIsAnyBoardMember(userId, boardsExpectedFalse)).to.equal(false); + expect(allowIsAnyBoardMember(Random.id(), boardsExpectedFalse)).to.equal(false); + }); + }); + + describe(allowIsBoardMemberCommentOnly.name, function() { + it('returns if a board has a member that is not comment-only member', function() { + const userId = Random.id(); + const board = { + hasMember: id => { + return id === userId; + }, + hasCommentOnly: id => { + return id !== userId; + } + }; + + expect(allowIsBoardMemberCommentOnly(userId, board)).to.equal(true); + expect(allowIsBoardMemberCommentOnly(Random.id(), board)).to.equal(false); + }); + }); + + describe(allowIsBoardMemberNoComments.name, function() { + it('returns if a board has a member that has comment any comments', function() { + const userId = Random.id(); + const board = { + hasMember: id => { + return id === userId; + }, + hasNoComments: id => { + return id !== userId; + } + }; + + expect(allowIsBoardMemberNoComments(userId, board)).to.equal(true); + expect(allowIsBoardMemberNoComments(Random.id(), board)).to.equal(false); + }); + }); + + describe(allowIsBoardMemberByCard.name, function() { + it('returns if the board for a given card has a member', function() { + const userId = Random.id(); + const board = { + hasMember: id => { + return id === userId; + } + }; + const card = { + board: () => board + }; + + expect(allowIsBoardMemberByCard(userId, card)).to.equal(true); + expect(allowIsBoardMemberByCard(Random.id(), card)).to.equal(false); + }); + }); +}); diff --git a/server/migrations.js b/server/migrations.js index b44899878..cfbe43c83 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -119,6 +119,10 @@ Migrations.add('use-css-class-for-boards-colors', () => { '#2C3E51': 'dark', '#27AE61': 'relax', '#568BA2': 'corteza', + '#499BEA': 'clearblue', + '#596557': 'natural', + '#2A80B8': 'modern', + '#2a2a2a': 'moderndark', }; Boards.find().forEach(board => { const oldBoardColor = board.background.color; @@ -243,19 +247,6 @@ Migrations.add('add-checklist-items', () => { }); }); -Migrations.add('add-profile-view', () => { - Users.find().forEach(user => { - if (!user.hasOwnProperty('profile.boardView')) { - // Set default view - Users.direct.update( - { _id: user._id }, - { $set: { 'profile.boardView': 'board-view-lists' } }, - noValidate, - ); - } - }); -}); - Migrations.add('add-card-types', () => { Cards.find().forEach(card => { Cards.direct.update( @@ -1033,3 +1024,40 @@ Migrations.add('add-description-text-allowed', () => { noValidateMulti, ); }); + +Migrations.add('add-sort-field-to-boards', () => { + Boards.find().forEach((board, index) => { + if (!board.hasOwnProperty('sort')) { + Boards.direct.update(board._id, { $set: { sort: index } }, noValidate); + } + }); +}); + +Migrations.add('add-default-profile-view', () => { + Users.find().forEach(user => { + if (!user.hasOwnProperty('profile.boardView')) { + // Set default view + Users.direct.update( + { _id: user._id }, + { $set: { 'profile.boardView': 'board-view-swimlanes' } }, + noValidate, + ); + } + }); +}); + +Migrations.add('add-hide-logo-by-default', () => { + Settings.update( + { + hideLogo: { + hideLogo: false, + }, + }, + { + $set: { + hideLogo: true, + }, + }, + noValidateMulti, + ); +}); diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js index 9a741ea1f..1de7824e6 100644 --- a/server/notifications/outgoing.js +++ b/server/notifications/outgoing.js @@ -61,6 +61,9 @@ if (Meteor.isServer) { 'card', 'commentId', 'swimlaneId', + 'customField', + 'customFieldValue', + 'attachmentId', ]; const responseFunc = data => { const paramCommentId = data.commentId; @@ -122,6 +125,7 @@ if (Meteor.isServer) { 'oldSwimlane', 'label', 'attachment', + 'attachmentId', ].forEach(key => { if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`; }); diff --git a/server/publications/attachments.js b/server/publications/attachments.js new file mode 100644 index 000000000..80ef954cc --- /dev/null +++ b/server/publications/attachments.js @@ -0,0 +1,60 @@ +import Attachments, { AttachmentStorage } from '/models/attachments'; +import { ObjectID } from 'bson'; + +Meteor.publish('attachmentsList', function() { + // eslint-disable-next-line no-console + // console.log('attachments:', AttachmentStorage.find()); + const files = AttachmentStorage.find( + {}, + { + fields: { + _id: 1, + filename: 1, + md5: 1, + length: 1, + contentType: 1, + metadata: 1, + }, + sort: { + filename: 1, + }, + limit: 250, + }, + ); + const attIds = []; + files.forEach(file => { + attIds.push(file._id._str); + }); + + return [ + files, + Attachments.find({ 'copies.attachments.key': { $in: attIds } }), + ]; +}); + +Meteor.publish('orphanedAttachments', function() { + let keys = []; + Attachments.find({}, { fields: { copies: 1 } }).forEach(att => { + keys.push(new ObjectID(att.copies.attachments.key)); + }); + keys.sort(); + keys = _.uniq(keys, true); + + return AttachmentStorage.find( + { _id: { $nin: keys } }, + { + fields: { + _id: 1, + filename: 1, + md5: 1, + length: 1, + contentType: 1, + metadata: 1, + }, + sort: { + filename: 1, + }, + limit: 250, + }, + ); +}); diff --git a/server/publications/boards.js b/server/publications/boards.js index f24dce47f..cfe93c0df 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -18,7 +18,7 @@ Meteor.publish('boards', function() { archived: false, $or: [ { - _id: { $in: starredBoards }, + // _id: { $in: starredBoards }, // Commented out, to get a list of all public boards permission: 'public', }, { members: { $elemMatch: { userId, isActive: true } } }, @@ -27,15 +27,20 @@ Meteor.publish('boards', function() { { fields: { _id: 1, + boardId: 1, archived: 1, slug: 1, title: 1, description: 1, color: 1, members: 1, + orgs: 1, + teams: 1, permission: 1, type: 1, + sort: 1, }, + sort: { sort: 1 /* boards default sorting */ }, }, ); }); @@ -60,7 +65,11 @@ Meteor.publish('archivedBoards', function() { archived: 1, slug: 1, title: 1, + createdAt: 1, + modifiedAt: 1, + archivedAt: 1, }, + sort: { archivedAt: -1, modifiedAt: -1 }, }, ); }); @@ -90,7 +99,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) { $or, // Sort required to ensure oplog usage }, - { limit: 1, sort: { _id: 1 } }, + { limit: 1, sort: { sort: 1 /* boards default sorting */ } }, ), function(boardId, board) { this.cursor(Lists.find({ boardId, archived: isArchived })); @@ -205,3 +214,19 @@ Meteor.publishRelations('board', function(boardId, isArchived) { return this.ready(); }); + +Meteor.methods({ + copyBoard(boardId, properties) { + check(boardId, String); + check(properties, Object); + + const board = Boards.findOne(boardId); + if (board) { + for (const key in properties) { + board[key] = properties[key]; + } + return board.copy(); + } + return null; + }, +}); diff --git a/server/publications/cards.js b/server/publications/cards.js index 61210ce56..ce35aff2f 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -1,4 +1,723 @@ +import moment from 'moment'; +import Users from '../../models/users'; +import Boards from '../../models/boards'; +import Lists from '../../models/lists'; +import Swimlanes from '../../models/swimlanes'; +import Cards from '../../models/cards'; +import CardComments from '../../models/cardComments'; +import Attachments from '../../models/attachments'; +import Checklists from '../../models/checklists'; +import ChecklistItems from '../../models/checklistItems'; +import SessionData from '../../models/usersessiondata'; +import CustomFields from '../../models/customFields'; +import { + DEFAULT_LIMIT, + OPERATOR_ASSIGNEE, + OPERATOR_BOARD, + OPERATOR_COMMENT, + OPERATOR_CREATED_AT, + OPERATOR_CREATOR, + OPERATOR_DUE, + OPERATOR_HAS, + OPERATOR_LABEL, + OPERATOR_LIMIT, + OPERATOR_LIST, + OPERATOR_MEMBER, + OPERATOR_MODIFIED_AT, + OPERATOR_SORT, + OPERATOR_STATUS, + OPERATOR_SWIMLANE, + OPERATOR_USER, + ORDER_ASCENDING, + PREDICATE_ALL, + PREDICATE_ARCHIVED, + PREDICATE_ASSIGNEES, + PREDICATE_ATTACHMENT, + PREDICATE_CHECKLIST, + PREDICATE_CREATED_AT, + PREDICATE_DESCRIPTION, + PREDICATE_DUE_AT, + PREDICATE_END_AT, + PREDICATE_ENDED, + PREDICATE_MEMBERS, + PREDICATE_MODIFIED_AT, + PREDICATE_PRIVATE, + PREDICATE_PUBLIC, + PREDICATE_START_AT, + PREDICATE_SYSTEM, +} from '/config/search-const'; +import { QueryErrors, QueryParams, Query } from '/config/query-classes'; + +const escapeForRegex = require('escape-string-regexp'); + Meteor.publish('card', cardId => { check(cardId, String); return Cards.find({ _id: cardId }); }); + +Meteor.publish('myCards', function(sessionId) { + check(sessionId, String); + + const queryParams = new QueryParams(); + queryParams.addPredicate(OPERATOR_USER, Meteor.user().username); + queryParams.setPredicate(OPERATOR_LIMIT, 200); + + const query = buildQuery(queryParams); + query.projection.sort = { + boardId: 1, + swimlaneId: 1, + listId: 1, + }; + + return findCards(sessionId, query); +}); + +// Meteor.publish('dueCards', function(sessionId, allUsers = false) { +// check(sessionId, String); +// check(allUsers, Boolean); +// +// // eslint-disable-next-line no-console +// // console.log('all users:', allUsers); +// +// const queryParams = { +// has: [{ field: 'dueAt', exists: true }], +// limit: 25, +// skip: 0, +// sort: { name: 'dueAt', order: 'des' }, +// }; +// +// if (!allUsers) { +// queryParams.users = [Meteor.user().username]; +// } +// +// return buildQuery(sessionId, queryParams); +// }); + +Meteor.publish('globalSearch', function(sessionId, params, text) { + check(sessionId, String); + check(params, Object); + check(text, String); + + // eslint-disable-next-line no-console + // console.log('queryParams:', params); + + return findCards(sessionId, buildQuery(new QueryParams(params, text))); +}); + +function buildSelector(queryParams) { + const userId = Meteor.userId(); + + const errors = new QueryErrors(); + + let selector = {}; + + // eslint-disable-next-line no-console + console.log('queryParams:', queryParams); + + if (queryParams.selector) { + selector = queryParams.selector; + } else { + const boardsSelector = {}; + + let archived = false; + let endAt = null; + if (queryParams.hasOperator(OPERATOR_STATUS)) { + queryParams.getPredicates(OPERATOR_STATUS).forEach(status => { + if (status === PREDICATE_ARCHIVED) { + archived = true; + } else if (status === PREDICATE_ALL) { + archived = null; + } else if (status === PREDICATE_ENDED) { + endAt = { $nin: [null, ''] }; + } else if ([PREDICATE_PRIVATE, PREDICATE_PUBLIC].includes(status)) { + boardsSelector.permission = status; + } + }); + } + selector = { + type: 'cardType-card', + // boardId: { $in: Boards.userBoardIds(userId) }, + $and: [], + }; + + if (archived !== null) { + if (archived) { + selector.boardId = { + $in: Boards.userBoardIds(userId, null, boardsSelector), + }; + selector.$and.push({ + $or: [ + { + boardId: { + $in: Boards.userBoardIds(userId, archived, boardsSelector), + }, + }, + { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } }, + { listId: { $in: Lists.archivedListIds() } }, + { archived: true }, + ], + }); + } else { + selector.boardId = { + $in: Boards.userBoardIds(userId, false, boardsSelector), + }; + selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() }; + selector.listId = { $nin: Lists.archivedListIds() }; + selector.archived = false; + } + } else { + selector.boardId = { + $in: Boards.userBoardIds(userId, null, boardsSelector), + }; + } + if (endAt !== null) { + selector.endAt = endAt; + } + + if (queryParams.hasOperator(OPERATOR_BOARD)) { + const queryBoards = []; + queryParams.getPredicates(OPERATOR_BOARD).forEach(query => { + const boards = Boards.userSearch(userId, { + title: new RegExp(escapeForRegex(query), 'i'), + }); + if (boards.count()) { + boards.forEach(board => { + queryBoards.push(board._id); + }); + } else { + errors.addNotFound(OPERATOR_BOARD, query); + } + }); + + selector.boardId.$in = queryBoards; + } + + if (queryParams.hasOperator(OPERATOR_SWIMLANE)) { + const querySwimlanes = []; + queryParams.getPredicates(OPERATOR_SWIMLANE).forEach(query => { + const swimlanes = Swimlanes.find({ + title: new RegExp(escapeForRegex(query), 'i'), + }); + if (swimlanes.count()) { + swimlanes.forEach(swim => { + querySwimlanes.push(swim._id); + }); + } else { + errors.addNotFound(OPERATOR_SWIMLANE, query); + } + }); + + // eslint-disable-next-line no-prototype-builtins + if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) { + selector.swimlaneId = { $in: [] }; + } + selector.swimlaneId.$in = querySwimlanes; + } + + if (queryParams.hasOperator(OPERATOR_LIST)) { + const queryLists = []; + queryParams.getPredicates(OPERATOR_LIST).forEach(query => { + const lists = Lists.find({ + title: new RegExp(escapeForRegex(query), 'i'), + }); + if (lists.count()) { + lists.forEach(list => { + queryLists.push(list._id); + }); + } else { + errors.addNotFound(OPERATOR_LIST, query); + } + }); + + // eslint-disable-next-line no-prototype-builtins + if (!selector.hasOwnProperty('listId')) { + selector.listId = { $in: [] }; + } + selector.listId.$in = queryLists; + } + + if (queryParams.hasOperator(OPERATOR_COMMENT)) { + const cardIds = CardComments.textSearch( + userId, + queryParams.getPredicates(OPERATOR_COMMENT), + com => { + return com.cardId; + }, + ); + if (cardIds.length) { + selector._id = { $in: cardIds }; + } else { + queryParams.getPredicates(OPERATOR_COMMENT).forEach(comment => { + errors.addNotFound(OPERATOR_COMMENT, comment); + }); + } + } + + [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].forEach(field => { + if (queryParams.hasOperator(field)) { + selector[field] = {}; + const predicate = queryParams.getPredicate(field); + selector[field][predicate.operator] = new Date(predicate.value); + } + }); + + const queryUsers = {}; + queryUsers[OPERATOR_ASSIGNEE] = []; + queryUsers[OPERATOR_MEMBER] = []; + queryUsers[OPERATOR_CREATOR] = []; + + if (queryParams.hasOperator(OPERATOR_USER)) { + const users = []; + queryParams.getPredicates(OPERATOR_USER).forEach(username => { + const user = Users.findOne({ username }); + if (user) { + users.push(user._id); + } else { + errors.addNotFound(OPERATOR_USER, username); + } + }); + if (users.length) { + selector.$and.push({ + $or: [{ members: { $in: users } }, { assignees: { $in: users } }], + }); + } + } + + [OPERATOR_MEMBER, OPERATOR_ASSIGNEE, OPERATOR_CREATOR].forEach(key => { + if (queryParams.hasOperator(key)) { + const users = []; + queryParams.getPredicates(key).forEach(username => { + const user = Users.findOne({ username }); + if (user) { + users.push(user._id); + } else { + errors.addNotFound(key, username); + } + }); + if (users.length) { + selector[key] = { $in: users }; + } + } + }); + + if (queryParams.hasOperator(OPERATOR_LABEL)) { + const queryLabels = []; + queryParams.getPredicates(OPERATOR_LABEL).forEach(label => { + let boards = Boards.userBoards(userId, null, { + labels: { $elemMatch: { color: label.toLowerCase() } }, + }); + + if (boards.count()) { + boards.forEach(board => { + // eslint-disable-next-line no-console + // console.log('board:', board); + // eslint-disable-next-line no-console + // console.log('board.labels:', board.labels); + board.labels + .filter(boardLabel => { + return boardLabel.color === label.toLowerCase(); + }) + .forEach(boardLabel => { + queryLabels.push(boardLabel._id); + }); + }); + } else { + // eslint-disable-next-line no-console + // console.log('label:', label); + const reLabel = new RegExp(escapeForRegex(label), 'i'); + // eslint-disable-next-line no-console + // console.log('reLabel:', reLabel); + boards = Boards.userBoards(userId, null, { + labels: { $elemMatch: { name: reLabel } }, + }); + + if (boards.count()) { + boards.forEach(board => { + board.labels + .filter(boardLabel => { + if (!boardLabel.name) { + return false; + } + return boardLabel.name.match(reLabel); + }) + .forEach(boardLabel => { + queryLabels.push(boardLabel._id); + }); + }); + } else { + errors.addNotFound(OPERATOR_LABEL, label); + } + } + }); + if (queryLabels.length) { + // eslint-disable-next-line no-console + // console.log('queryLabels:', queryLabels); + selector.labelIds = { $in: _.uniq(queryLabels) }; + } + } + + if (queryParams.hasOperator(OPERATOR_HAS)) { + queryParams.getPredicates(OPERATOR_HAS).forEach(has => { + switch (has.field) { + case PREDICATE_ATTACHMENT: + selector.$and.push({ + _id: { + $in: Attachments.find({}, { fields: { cardId: 1 } }).map( + a => a.cardId, + ), + }, + }); + break; + case PREDICATE_CHECKLIST: + selector.$and.push({ + _id: { + $in: Checklists.find({}, { fields: { cardId: 1 } }).map( + a => a.cardId, + ), + }, + }); + break; + case PREDICATE_DESCRIPTION: + case PREDICATE_START_AT: + case PREDICATE_DUE_AT: + case PREDICATE_END_AT: + if (has.exists) { + selector[has.field] = { $exists: true, $nin: [null, ''] }; + } else { + selector[has.field] = { $in: [null, ''] }; + } + break; + case PREDICATE_ASSIGNEES: + case PREDICATE_MEMBERS: + if (has.exists) { + selector[has.field] = { $exists: true, $nin: [null, []] }; + } else { + selector[has.field] = { $in: [null, []] }; + } + break; + } + }); + } + + if (queryParams.text) { + const regex = new RegExp(escapeForRegex(queryParams.text), 'i'); + + const items = ChecklistItems.find( + { title: regex }, + { fields: { cardId: 1 } }, + ); + const checklists = Checklists.find( + { + $or: [ + { title: regex }, + { _id: { $in: items.map(item => item.checklistId) } }, + ], + }, + { fields: { cardId: 1 } }, + ); + + const attachments = Attachments.find({ 'original.name': regex }); + + const comments = CardComments.find( + { text: regex }, + { fields: { cardId: 1 } }, + ); + + selector.$and.push({ + $or: [ + { title: regex }, + { description: regex }, + { customFields: { $elemMatch: { value: regex } } }, + // { + // _id: { + // $in: CardComments.textSearch(userId, [queryParams.text]).map( + // com => com.cardId, + // ), + // }, + // }, + { _id: { $in: checklists.map(list => list.cardId) } }, + { _id: { $in: attachments.map(attach => attach.cardId) } }, + { _id: { $in: comments.map(com => com.cardId) } }, + ], + }); + } + + if (selector.$and.length === 0) { + delete selector.$and; + } + } + + // eslint-disable-next-line no-console + console.log('selector:', selector); + // eslint-disable-next-line no-console + console.log('selector.$and:', selector.$and); + + const query = new Query(); + query.selector = selector; + query.setQueryParams(queryParams); + query._errors = errors; + + return query; +} + +function buildProjection(query) { + // eslint-disable-next-line no-console + // console.log('query:', query); + let skip = 0; + if (query.getQueryParams().skip) { + skip = query.getQueryParams().skip; + } + let limit = DEFAULT_LIMIT; + const configLimit = parseInt(process.env.RESULTS_PER_PAGE, 10); + if (!isNaN(configLimit) && configLimit > 0) { + limit = configLimit; + } + + if (query.getQueryParams().hasOperator(OPERATOR_LIMIT)) { + limit = query.getQueryParams().getPredicate(OPERATOR_LIMIT); + } + + const projection = { + fields: { + _id: 1, + archived: 1, + boardId: 1, + swimlaneId: 1, + listId: 1, + title: 1, + type: 1, + sort: 1, + members: 1, + assignees: 1, + colors: 1, + dueAt: 1, + createdAt: 1, + modifiedAt: 1, + labelIds: 1, + customFields: 1, + userId: 1, + }, + sort: { + boardId: 1, + swimlaneId: 1, + listId: 1, + sort: 1, + }, + skip, + limit, + }; + + if (query.getQueryParams().hasOperator(OPERATOR_SORT)) { + const order = + query.getQueryParams().getPredicate(OPERATOR_SORT).order === + ORDER_ASCENDING + ? 1 + : -1; + switch (query.getQueryParams().getPredicate(OPERATOR_SORT).name) { + case PREDICATE_DUE_AT: + projection.sort = { + dueAt: order, + boardId: 1, + swimlaneId: 1, + listId: 1, + sort: 1, + }; + break; + case PREDICATE_MODIFIED_AT: + projection.sort = { + modifiedAt: order, + boardId: 1, + swimlaneId: 1, + listId: 1, + sort: 1, + }; + break; + case PREDICATE_CREATED_AT: + projection.sort = { + createdAt: order, + boardId: 1, + swimlaneId: 1, + listId: 1, + sort: 1, + }; + break; + case PREDICATE_SYSTEM: + projection.sort = { + boardId: order, + swimlaneId: order, + listId: order, + modifiedAt: order, + sort: order, + }; + break; + } + } + + // eslint-disable-next-line no-console + // console.log('projection:', projection); + + query.projection = projection; + + return query; +} + +function buildQuery(queryParams) { + const query = buildSelector(queryParams); + + return buildProjection(query); +} + +Meteor.publish('brokenCards', function(sessionId) { + check(sessionId, String); + + const params = new QueryParams(); + params.addPredicate(OPERATOR_STATUS, PREDICATE_ALL); + const query = buildQuery(params); + query.selector.$or = [ + { boardId: { $in: [null, ''] } }, + { swimlaneId: { $in: [null, ''] } }, + { listId: { $in: [null, ''] } }, + ]; + // console.log('brokenCards selector:', query.selector); + + return findCards(sessionId, query); +}); + +Meteor.publish('nextPage', function(sessionId) { + check(sessionId, String); + + const session = SessionData.findOne({ sessionId }); + const projection = session.getProjection(); + projection.skip = session.lastHit; + + return findCards(sessionId, new Query(session.getSelector(), projection)); +}); + +Meteor.publish('previousPage', function(sessionId) { + check(sessionId, String); + + const session = SessionData.findOne({ sessionId }); + const projection = session.getProjection(); + projection.skip = session.lastHit - session.resultsCount - projection.limit; + + return findCards(sessionId, new Query(session.getSelector(), projection)); +}); + +function findCards(sessionId, query) { + const userId = Meteor.userId(); + + // eslint-disable-next-line no-console + // console.log('selector:', query.selector); + // console.log('selector.$and:', query.selector.$and); + // eslint-disable-next-line no-console + // console.log('projection:', projection); + + const cards = Cards.find(query.selector, query.projection); + // eslint-disable-next-line no-console + // console.log('count:', cards.count()); + + const update = { + $set: { + totalHits: 0, + lastHit: 0, + resultsCount: 0, + cards: [], + selector: SessionData.pickle(query.selector), + projection: SessionData.pickle(query.projection), + errors: query.errors(), + }, + }; + + if (cards) { + update.$set.totalHits = cards.count(); + update.$set.lastHit = + query.projection.skip + query.projection.limit < cards.count() + ? query.projection.skip + query.projection.limit + : cards.count(); + update.$set.cards = cards.map(card => { + return card._id; + }); + update.$set.resultsCount = update.$set.cards.length; + } + + // eslint-disable-next-line no-console + // console.log('sessionId:', sessionId); + // eslint-disable-next-line no-console + // console.log('userId:', userId); + // eslint-disable-next-line no-console + // console.log('update:', update); + SessionData.upsert({ userId, sessionId }, update); + + // remove old session data + SessionData.remove({ + userId, + modifiedAt: { + $lt: new Date( + moment() + .subtract(1, 'day') + .format(), + ), + }, + }); + + if (cards) { + const boards = []; + const swimlanes = []; + const lists = []; + const customFieldIds = []; + const users = [this.userId]; + + cards.forEach(card => { + if (card.boardId) boards.push(card.boardId); + if (card.swimlaneId) swimlanes.push(card.swimlaneId); + if (card.listId) lists.push(card.listId); + if (card.userId) { + users.push(card.userId); + } + if (card.members) { + card.members.forEach(userId => { + users.push(userId); + }); + } + if (card.assignees) { + card.assignees.forEach(userId => { + users.push(userId); + }); + } + if (card.customFields) { + card.customFields.forEach(field => { + customFieldIds.push(field._id); + }); + } + }); + + const fields = { + _id: 1, + title: 1, + archived: 1, + sort: 1, + type: 1, + }; + + return [ + cards, + Boards.find( + { _id: { $in: boards } }, + { fields: { ...fields, labels: 1, color: 1 } }, + ), + Swimlanes.find( + { _id: { $in: swimlanes } }, + { fields: { ...fields, color: 1 } }, + ), + Lists.find({ _id: { $in: lists } }, { fields }), + CustomFields.find({ _id: { $in: customFieldIds } }), + Users.find({ _id: { $in: users } }, { fields: Users.safeFields }), + Checklists.find({ cardId: { $in: cards.map(c => c._id) } }), + Attachments.find({ cardId: { $in: cards.map(c => c._id) } }), + CardComments.find({ cardId: { $in: cards.map(c => c._id) } }), + SessionData.find({ userId, sessionId }), + ]; + } + + return [SessionData.find({ userId, sessionId })]; +} diff --git a/server/publications/org.js b/server/publications/org.js new file mode 100644 index 000000000..8c4252edd --- /dev/null +++ b/server/publications/org.js @@ -0,0 +1,27 @@ +Meteor.publish('org', function(query, limit) { + check(query, Match.OneOf(Object, null)); + check(limit, Number); + + if (!Match.test(this.userId, String)) { + return []; + } + + const user = Users.findOne(this.userId); + if (user && user.isAdmin) { + return Org.find(query, { + limit, + sort: { createdAt: -1 }, + fields: { + orgDisplayName: 1, + orgDesc: 1, + orgShortName: 1, + orgWebsite: 1, + orgTeams: 1, + createdAt: 1, + orgIsActive: 1, + }, + }); + } + + return []; +}); diff --git a/server/publications/people.js b/server/publications/people.js index 0a7ef6ee6..af8dfcea5 100644 --- a/server/publications/people.js +++ b/server/publications/people.js @@ -14,11 +14,15 @@ Meteor.publish('people', function(query, limit) { fields: { username: 1, 'profile.fullname': 1, + 'profile.initials': 1, isAdmin: 1, emails: 1, createdAt: 1, loginDisabled: 1, authenticationMethod: 1, + importUsernames: 1, + orgs: 1, + teams: 1, }, }); } diff --git a/server/publications/rules.js b/server/publications/rules.js index 2a593067c..1b4ce0ca0 100644 --- a/server/publications/rules.js +++ b/server/publications/rules.js @@ -1,3 +1,8 @@ +import Boards from '/models/boards'; +import Actions from '/models/actions'; +import Triggers from '/models/triggers'; +import Rules from '/models/rules'; + Meteor.publish('rules', ruleId => { check(ruleId, String); return Rules.find({ @@ -16,3 +21,23 @@ Meteor.publish('allTriggers', () => { Meteor.publish('allActions', () => { return Actions.find({}); }); + +Meteor.publish('rulesReport', () => { + const rules = Rules.find(); + const actionIds = []; + const triggerIds = []; + const boardIds = []; + + rules.forEach(rule => { + actionIds.push(rule.actionId); + triggerIds.push(rule.triggerId); + boardIds.push(rule.boardId); + }); + + return [ + rules, + Actions.find({ _id: { $in: actionIds } }), + Triggers.find({ _id: { $in: triggerIds } }), + Boards.find({ _id: { $in: boardIds } }, { fields: { title: 1 } }), + ]; +}); diff --git a/server/publications/settings.js b/server/publications/settings.js index 034737e7c..84234e31b 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -12,10 +12,18 @@ Meteor.publish('setting', () => { disableRegistration: 1, productName: 1, hideLogo: 1, + customLoginLogoImageUrl: 1, + customLoginLogoLinkUrl: 1, + textBelowCustomLoginLogo: 1, + automaticLinkedUrlSchemes: 1, + customTopLeftCornerLogoImageUrl: 1, + customTopLeftCornerLogoLinkUrl: 1, + customTopLeftCornerLogoHeight: 1, customHTMLafterBodyStart: 1, customHTMLbeforeBodyEnd: 1, displayAuthenticationMethod: 1, defaultAuthenticationMethod: 1, + spinnerName: 1, }, }, ); @@ -25,7 +33,18 @@ Meteor.publish('mailServer', function() { if (!Match.test(this.userId, String)) return []; const user = Users.findOne(this.userId); if (user && user.isAdmin) { - return Settings.find({}, { fields: { mailServer: 1 } }); + return Settings.find( + {}, + { + fields: { + 'mailServer.host': 1, + 'mailServer.port': 1, + 'mailServer.username': 1, + 'mailServer.enableTLS': 1, + 'mailServer.from': 1, + }, + }, + ); } return []; }); diff --git a/server/publications/swimlanes.js b/server/publications/swimlanes.js new file mode 100644 index 000000000..4c119866a --- /dev/null +++ b/server/publications/swimlanes.js @@ -0,0 +1,32 @@ +Meteor.methods({ + copySwimlane(swimlaneId, toBoardId) { + check(swimlaneId, String); + check(toBoardId, String); + + const swimlane = Swimlanes.findOne(swimlaneId); + const toBoard = Boards.findOne(toBoardId); + + if (swimlane && toBoard) { + swimlane.copy(toBoardId); + return true; + } + + return false; + }, + + moveSwimlane(swimlaneId, toBoardId) { + check(swimlaneId, String); + check(toBoardId, String); + + const swimlane = Swimlanes.findOne(swimlaneId); + const toBoard = Boards.findOne(toBoardId); + + if (swimlane && toBoard) { + swimlane.move(toBoardId); + + return true; + } + + return false; + }, +}); diff --git a/server/publications/team.js b/server/publications/team.js new file mode 100644 index 000000000..6877a8266 --- /dev/null +++ b/server/publications/team.js @@ -0,0 +1,27 @@ +Meteor.publish('team', function(query, limit) { + check(query, Match.OneOf(Object, null)); + check(limit, Number); + + if (!Match.test(this.userId, String)) { + return []; + } + + const user = Users.findOne(this.userId); + if (user && user.isAdmin) { + return Team.find(query, { + limit, + sort: { createdAt: -1 }, + fields: { + teamDisplayName: 1, + teamDesc: 1, + teamShortName: 1, + teamWebsite: 1, + teams: 1, + createdAt: 1, + teamIsActive: 1, + }, + }); + } + + return []; +}); diff --git a/server/publications/users.js b/server/publications/users.js index c04f8c5c1..152669064 100644 --- a/server/publications/users.js +++ b/server/publications/users.js @@ -1,14 +1,22 @@ -Meteor.publish('user-miniprofile', function(userId) { - check(userId, String); +Meteor.publish('user-miniprofile', function(usernames) { + check(usernames, Array); - return Users.find(userId, { - fields: { - username: 1, - 'profile.fullname': 1, - 'profile.avatarUrl': 1, - 'profile.initials': 1, + // eslint-disable-next-line no-console + // console.log('usernames:', usernames); + return Users.find( + { + $or: [ + { username: { $in: usernames } }, + { importUsernames: { $in: usernames } }, + ], }, - }); + { + fields: { + ...Users.safeFields, + importUsernames: 1, + }, + }, + ); }); Meteor.publish('user-admin', function() { diff --git a/server/rulesHelper.js b/server/rulesHelper.js index 63e330ab2..e2f577826 100644 --- a/server/rulesHelper.js +++ b/server/rulesHelper.js @@ -29,14 +29,26 @@ RulesHelper = { }, buildMatchingFieldsMap(activity, matchingFields) { const matchingMap = { activityType: activity.activityType }; - for (let i = 0; i < matchingFields.length; i++) { + matchingFields.forEach(field => { // Creating a matching map with the actual field of the activity // and with the wildcard (for example: trigger when a card is added // in any [*] board - matchingMap[matchingFields[i]] = { - $in: [activity[matchingFields[i]], '*'], + let value = activity[field]; + if (field === 'oldListName') { + const oldList = Lists.findOne({ _id: activity.oldListId }); + if (oldList) { + value = oldList.title; + } + } else if (field === 'oldSwimlaneName') { + const oldSwimlane = Swimlanes.findOne({ _id: activity.oldSwimlaneId }); + if (oldSwimlane) { + value = oldSwimlane.title; + } + } + matchingMap[field] = { + $in: [value, '*'], }; - } + }); return matchingMap; }, performAction(activity, action) { diff --git a/server/saml.js b/server/saml.js new file mode 100644 index 000000000..758bdf28c --- /dev/null +++ b/server/saml.js @@ -0,0 +1,5 @@ +Meteor.startup(() => { + if (process.env.SAML_PROVIDER !== '') { + Meteor.settings.public.SAML_PROVIDER = process.env.SAML_PROVIDER; + } +}); diff --git a/server/scroll.js b/server/scroll.js deleted file mode 100644 index c2cc797ea..000000000 --- a/server/scroll.js +++ /dev/null @@ -1,15 +0,0 @@ -Meteor.startup(() => { - // Mouse Scroll Intertia, issue #2949. Integer. - if (process.env.SCROLLINERTIA !== '0') { - Meteor.settings.public.SCROLLINERTIA = process.env.SCROLLINERTIA; - } else { - Meteor.settings.public.SCROLLINERTIA = 0; - } - - // Mouse Scroll Amount, issue #2949. "auto" or Integer. - if (process.env.SCROLLAMOUNT !== 'auto') { - Meteor.settings.public.SCROLLAMOUNT = process.env.SCROLLAMOUNT; - } else { - Meteor.settings.public.SCROLLAMOUNT = 'auto'; - } -}); diff --git a/server/spinner.js b/server/spinner.js new file mode 100644 index 000000000..3697ae232 --- /dev/null +++ b/server/spinner.js @@ -0,0 +1,3 @@ +Meteor.startup(() => { + Meteor.settings.public.WAIT_SPINNER = process.env.WAIT_SPINNER; +}); diff --git a/snap-src/bin/config b/snap-src/bin/config index 3fc786fb8..83ce687be 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -3,14 +3,18 @@ # All supported keys are defined here together with descriptions and default values # list of supported keys -keys="DEBUG MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD ATTACHMENTS_STORE_PATH SCROLLINERTIA SCROLLAMOUNT" +keys="DEBUG MONGO_LOG_DESTINATION MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OAUTH2_CA_CERT OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS OAUTH2_ADFS_ENABLED LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD ATTACHMENTS_STORE_PATH PASSWORD_LOGIN_ENABLED CAS_ENABLED CAS_BASE_URL CAS_LOGIN_URL CAS_VALIDATE_URL SAML_ENABLED SAML_PROVIDER SAML_ENTRYPOINT SAML_ISSUER SAML_CERT SAML_IDPSLO_REDIRECTURL SAML_PRIVATE_KEYFILE SAML_PUBLIC_CERTFILE SAML_IDENTIFIER_FORMAT SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE SAML_ATTRIBUTES ORACLE_OIM_ENABLED RESULTS_PER_PAGE WAIT_SPINNER" # default values DESCRIPTION_DEBUG="Debug OIDC OAuth2 etc. Example: sudo snap set wekan debug='true'" DEFAULT_DEBUG="false" KEY_DEBUG="debug" -DESCRIPTION_MONGOURL="MONGO_URL. Default: ''" +DESCRIPTION_MONGO_LOG_DESTINATION="MONGO_LOG_DESTINATION: devnull/snapcommon/syslog. Default: 'devnull'" +DEFAULT_MONGO_LOG_DESTINATION="devnull" +KEY_MONGO_LOG_DESTINATION='mongo-log-destination' + +DESCRIPTION_MONGO_URL="MONGO_URL. Default: ''" DEFAULT_MONGO_URL="" KEY_MONGO_URL='mongo-url' @@ -96,6 +100,8 @@ DESCRIPTION_ATTACHMENTS_STORE_PATH="Allow wekan ower to specify where uploaded f DEFAULT_ATTACHMENTS_STORE_PATH="" KEY_ATTACHMENTS_STORE_PATH="attachments-store-path" +# Example, not in use: /var/snap/wekan/common/uploads/ + DESCRIPTION_MAX_IMAGE_PIXEL="Max image pixel: Allow to shrink attached/pasted image https://github.com/wekan/wekan/pull/2544" DEFAULT_MAX_IMAGE_PIXEL="" KEY_MAX_IMAGE_PIXEL="max-image-pixel" @@ -104,6 +110,10 @@ DESCRIPTION_IMAGE_COMPRESS_RATIO="Image compress ratio: Allow to shrink attached DEFAULT_IMAGE_COMPRESS_RATIO="" KEY_IMAGE_COMPRESS_RATIO="image-compress-ratio" +DESCRIPTION_NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="Number of days after a notification is read before we remove it. Default: 2." +DEFAULT_NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" +KEY_NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="notification-tray-after-read-days-before-remove" + DESCRIPTION_BIGEVENTS_PATTERN="Big events pattern: Notify always due etc regardless of notification settings. Default: NONE, All: received|start|due|end, Disabled: NONE" DEFAULT_BIGEVENTS_PATTERN="NONE" KEY_BIGEVENTS_PATTERN="bigevents-pattern" @@ -162,10 +172,22 @@ DESCRIPTION_WEBHOOKS_ATTRIBUTES="What to send to Outgoing Webhook, or leave out. DEFAULT_WEBHOOKS_ATTRIBUTES="" KEY_WEBHOOKS_ATTRIBUTES="webhooks-attributes" -DESCRIPTION_OAUTH2_ENABLED="Enable the OAuth2 connection" +DESCRIPTION_ORACLE_OIM_ENABLED="Enable OAUTH2 ORACLE on premise identity manager OIM. Default: false" +DEFAULT_ORACLE_OIM_ENABLED="false" +KEY_ORACLE_OIM_ENABLED="oracle-oim-enabled" + +DESCRIPTION_OAUTH2_ENABLED="Enable the OAuth2 connection. Default: false" DEFAULT_OAUTH2_ENABLED="false" KEY_OAUTH2_ENABLED="oauth2-enabled" +DESCRIPTION_OAUTH2_CA_CERT="Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299." +DEFAULT_OAUTH2_CA_CERT="" +KEY_OAUTH2_CA_CERT="oauth2-ca-cert" + +DESCRIPTION_OAUTH2_ADFS_ENABLED="Enable OAuth2 ADFS. Default: false" +DEFAULT_OAUTH2_ADFS_ENABLED="false" +KEY_OAUTH2_ADFS_ENABLED="oauth2-adfs-enabled" + DESCRIPTION_OAUTH2_LOGIN_STYLE="OAuth2 login style: popup or redirect. Default: redirect" DEFAULT_OAUTH2_LOGIN_STYLE="redirect" KEY_OAUTH2_LOGIN_STYLE="oauth2-login-style" @@ -450,10 +472,82 @@ DESCRIPTION_DEFAULT_AUTHENTICATION_METHOD="The default authentication method use DEFAULT_DEFAULT_AUTHENTICATION_METHOD="" KEY_DEFAULT_AUTHENTICATION_METHOD="default-authentication-method" -DESCRIPTION_SCROLLINERTIA="Mousewheel scroll inertia, issue #2949. Default: 0" -DEFAULT_SCROLLINERTIA="0" -KEY_SCROLLINERTIA="scrollinertia" +DESCRIPTION_PASSWORD_LOGIN_ENABLED="To hide the password login form" +DEFAULT_PASSWORD_LOGIN_ENABLED="true" +KEY_PASSWORD_LOGIN_ENABLED="password-login-enabled" -DESCRIPTION_SCROLLINERTIA="Mousewheel scroll amount, issue #2949. Default: 'auto'" -DEFAULT_SCROLLINERTIA="auto" -KEY_SCROLLINERTIA="scrollamount" +DESCRIPTION_CAS_ENABLED="CAS Enabled" +DEFAULT_CAS_ENABLED="false" +KEY_CAS_ENABLED="cas-enabled" + +DESCRIPTION_CAS_BASE_URL="CAS Base URL" +DEFAULT_CAS_BASE_URL="" +KEY_CAS_BASE_URL="cas-base-url" + +DESCRIPTION_CAS_LOGIN_URL="CAS Login URL" +DEFAULT_CAS_LOGIN_URL="" +KEY_CAS_LOGIN_URL="cas-login-url" + +DESCRIPTION_CAS_VALIDATE_URL="CAS Validate URL" +DEFAULT_CAS_VALIDATE_URL="" +KEY_CAS_VALIDATE_URL="cas-validate-url" + +DESCRIPTION_SAML_ENABLED="SAML Enabled. Default: false" +DEFAULT_SAML_ENABLED="false" +KEY_SAML_ENABLED="saml-enabled" + +DESCRIPTION_SAML_PROVIDER="SAML Provider" +DEFAULT_SAML_PROVIDER="" +KEY_SAML_PROVIDER="saml-provider" + +DESCRIPTION_SAML_ENTRYPOINT="SAML Entrypoint" +DEFAULT_SAML_ENTRYPOINT="" +KEY_SAML_ENTRYPOINT="saml-entrypoint" + +DESCRIPTION_SAML_ISSUER="SAML Issuer" +DEFAULT_SAML_ISSUER="" +KEY_SAML_ISSUER="saml-issuer" + +DESCRIPTION_SAML_CERT="SAML Cert" +DEFAULT_SAML_CERT="" +KEY_SAML_CERT="saml-cert" + +DESCRIPTION_SAML_IDPSLO_REDIRECTURL="SAML IDPSLO Redirect URL" +DEFAULT_SAML_IDPSLO_REDIRECTURL="" +KEY_SAML_IDPSLO_REDIRECTURL="saml-idpslo-redirecturl" + +DESCRIPTION_SAML_PRIVATE_KEYFILE="SAML Private Keyfile" +DEFAULT_SAML_PRIVATE_KEYFILE="" +KEY_SAML_PRIVATE_KEYFILE="saml-private-keyfile" + +DESCRIPTION_SAML_PUBLIC_CERTFILE="SAML Public Certfile" +DEFAULT_SAML_PUBLIC_CERTFILE="" +KEY_SAML_PUBLIC_CERTFILE="saml-public-certfile" + +DESCRIPTION_SAML_PUBLIC_CERTFILE="SAML Public Certfile" +DEFAULT_SAML_PUBLIC_CERTFILE="" +KEY_SAML_PUBLIC_CERTFILE="saml-public-certfile" + +DESCRIPTION_SAML_IDENTIFIER_FORMAT="SAML Identifier Format" +DEFAULT_SAML_IDENTIFIER_FORMAT="" +KEY_SAML_IDENTIFIER_FORMAT="saml-identifier-format" + +DESCRIPTION_SAML_IDENTIFIER_FORMAT="SAML Identifier Format" +DEFAULT_SAML_IDENTIFIER_FORMAT="" +KEY_SAML_IDENTIFIER_FORMAT="saml-identifier-format" + +DESCRIPTION_SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="SAML Local Profile Match Attribute" +DEFAULT_SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" +KEY_SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="saml-local-profile-match-attribute" + +DESCRIPTION_SAML_ATTRIBUTES="SAML Attributes" +DEFAULT_SAML_ATTRIBUTES="" +KEY_SAML_ATTRIBUTES="saml-attributes" + +DESCRIPTION_RESULTS_PER_PAGE="Number of search results to show per page by default" +DEFAULT_RESULTS_PER_PAGE="" +KEY_RESULTS_PER_PAGE="results-per-page" + +DESCRIPTION_WAIT_SPINNER="Default wait spinner to use" +DEFAULT_WAIT_SPINNER="Bounce" +KEY_WAIT_SPINNER="wait-spinner" diff --git a/snap-src/bin/mongodb-control b/snap-src/bin/mongodb-control index 246c3acba..ec7202559 100755 --- a/snap-src/bin/mongodb-control +++ b/snap-src/bin/mongodb-control @@ -24,6 +24,12 @@ if test -f "$SNAP_COMMON/mongodb.log"; then rm -f "$SNAP_COMMON/mongodb.log" fi +# Not in use. If uploads directory does not exist, create it. +# Wekan will store attachments there. +#if [ ! -d "$SNAP_COMMON/uploads" ]; then +# mkdir "$SNAP_COMMON/uploads" +#fi + # Alternative: When starting MongoDB, and using logfile, truncate log to last 1000 lines of text. # 1) If file exists: #if test -f "$SNAP_COMMON/mongodb.log"; then @@ -50,19 +56,45 @@ if [ -z "$MONGO_URL" ]; then fi echo "mongodb bind options: $BIND_OPTIONS" - ## OLD: Logging to file. - #mongod --dbpath $SNAP_COMMON --logpath $SNAP_COMMON/mongodb.log --logappend --journal $BIND_OPTIONS --smallfiles - ## NEW: Logging to syslog, that usually has already log rotation. - mongod --dbpath $SNAP_COMMON --syslog --journal $BIND_OPTIONS --quiet + if [ "syslog" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to syslog" + mongod --dbpath $SNAP_COMMON --syslog --journal $BIND_OPTIONS --quiet + exit 0 + fi + + if [ "snapcommon" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to $SNAP_COMMON" + mongod --dbpath $SNAP_COMMON --logpath $SNAP_COMMON/mongodb.log --logappend --journal $BIND_OPTIONS --quiet + fi + + if [ "devnull" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to /dev/null" + mongod --dbpath $SNAP_COMMON --logpath /dev/null --journal $BIND_OPTIONS --quiet + fi + + # Drop indexes on database upgrade, when starting MongoDB + #mongo wekan --eval "db.getCollectionNames().forEach(function(col_name) { var coll = db.getCollection(col_name); coll.dropIndexes(); });" $BIND_OPTIONS + mongo wekan --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.4" });' $BIND_OPTIONS else - ## OLD: Logging to file. - #mongod --dbpath $SNAP_COMMON --logpath $SNAP_COMMON/mongodb.log --logappend --journal $MONGO_URL --smallfiles - ## NEW: Logging to syslog, that usually has already log rotation. - mongod --dbpath $SNAP_COMMON --syslog --journal $MONGO_URL --quiet + if [ "syslog" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to syslog" + mongod --dbpath $SNAP_COMMON --syslog --journal $MONGO_URL --quiet + fi + + if [ "snapcommon" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to $SNAP_COMMON" + mongod --dbpath $SNAP_COMMON --logpath $SNAP_COMMON/mongodb.log --logappend --journal $MONGO_URL --quiet + fi + + if [ "devnull" == "${MONGO_LOG_DESTINATION}" ]; then + echo "Sending mongodb logs to /dev/null" + mongod --dbpath $SNAP_COMMON --logpath /dev/null --journal $MONGO_URL --quiet + fi + + # Drop indexes on database upgrade, when starting MongoDB + #mongo wekan --eval "db.getCollectionNames().forEach(function(col_name) { var coll = db.getCollection(col_name); coll.dropIndexes(); });" $BIND_OPTIONS + mongo wekan --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.2" });' $BIND_OPTIONS fi - -# Drop indexes on database upgrade, when starting MongoDB -#mongo wekan --eval "db.getCollectionNames().forEach(function(col_name) { var coll = db.getCollection(col_name); coll.dropIndexes(); });" $BIND_OPTIONS diff --git a/snap-src/bin/wekan-help b/snap-src/bin/wekan-help index 1d6d87a1d..3f7a4c91c 100755 --- a/snap-src/bin/wekan-help +++ b/snap-src/bin/wekan-help @@ -13,12 +13,16 @@ echo -e "Debug OIDC OAuth2 etc." echo -e "To enable the Debug of Wekan:" echo -e "\t$ snap set $SNAP_NAME debug='true'" echo -e "\t-Disable the Debug of Wekan:" -echo -e "\t$ snap set $SNAP_NAME debug='false'" +echo -e "\t$ snap unset $SNAP_NAME debug" +echo -e "\n" +echo -e "Mongo log destimation: devnull/snapcommon/syslog. Default: 'devnull'" +echo -e "To set different mongo log destination of Wekan:" +echo -e "\t$ snap set $SNAP_NAME mongo-log-destination='snapcommon'" echo -e "\n" echo -e "To enable the MONGO_URL of Wekan:" echo -e "\t$ snap set $SNAP_NAME mongo-url='...'" echo -e "\t-Disable the MONGO_URL of Wekan:" -echo -e "\t$ snap set $SNAP_NAME mongo-url=''" +echo -e "\t$ snap unset $SNAP_NAME mongo-url" echo -e "\n" echo -e "Make sure you have connected all interfaces, check more by calling $ snap interfaces ${SNAP_NAME}" echo -e "\n" @@ -43,67 +47,79 @@ echo -e "\n" echo -e "To enable the API of wekan:" echo -e "\t$ snap set $SNAP_NAME with-api='true'" echo -e "\t-Disable the API:" -echo -e "\t$ snap set $SNAP_NAME with-api='false'" +echo -e "\t$ snap unset $SNAP_NAME with-api" +echo -e "\n" +echo -e "Number of search results to show per page by default:" +echo -e "\t$ snap set $SNAP_NAME results-per-page='20'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME results-per-page" echo -e "\n" echo -e "Accounts lockout known users failures before, greater than 0. Default: 3" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-known-users-failures-before='3'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-known-users-failures-before" echo -e "\n" echo -e "Accounts lockout know users period, in seconds. Default: 60" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-known-users-period='60'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-known-users-period" echo -e "\n" echo -e "Accounts lockout unknown failure window, in seconds. Default: 15" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-known-users-failure-window='15'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-known-users-failure-window" echo -e "\n" echo -e "Accounts lockout unknown users failures before, greater than 0. Default: 3" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-unknown-users-failures-before='3'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-unknown-users-failures-before" echo -e "\n" echo -e "Accounts lockout unknown users lockout period, in seconds. Default: 60" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-unknown-users-lockout-period='60'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-unknown-users-lockout-period" echo -e "\n" echo -e "Accounts lockout unknown users failure window, in seconds. Default: 15" echo -e "\t$ snap set $SNAP_NAME accounts-lockout-unknown-users-failure-window='15'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME accounts-lockout-unknown-users-failure-window" echo -e "\n" echo -e "Rich text editor in card comments. Default: false https://github.com/wekan/wekan/pull/2560" -echo -e "Default:" +echo -e "Enable:" echo -e "\t$ snap set $SNAP_NAME richer-card-comment-editor='true'" -echo -e "Disabled:" -echo -e "\t$ snap set $SNAP_NAME richer-card-comment-editor='false'" -echo -e "\n" -echo -e "Mousewheel scroll inertia. Default: 0. https://github.com/wekan/wekan/issues/2949" -echo -e "Enable:" -echo -e "\t$ snap set $SNAP_NAME scrollinertia='950'" -echo -e "Disable, default:" -echo -e "\t$ snap set $SNAP_NAME scrollinertia='0'" -echo -e "\n" -echo -e "Mousewheel scroll amount. Default: 'auto'. Allowed: 'auto' or Integer number. https://github.com/wekan/wekan/issues/2949" -echo -e "Enable:" -echo -e "\t$ snap set $SNAP_NAME scrollamount='950'" -echo -e "Disable, default:" -echo -e "\t$ snap set $SNAP_NAME scrollamount='auto'" +echo -e "Disable:" +echo -e "\t$ snap unset $SNAP_NAME richer-card-comment-editor" echo -e "\n" echo -e "Card opened, send webhook message. Default: false https://github.com/wekan/wekan/issues/2518" echo -e "Enable:" echo -e "\t$ snap set $SNAP_NAME card-opened-webhook-enabled='true'" echo -e "Disable, default:" -echo -e "\t$ snap set $SNAP_NAME card-opened-webhook-enabled='false'" +echo -e "\t$ snap unset $SNAP_NAME card-opened-webhook-enabled" echo -e "\n" echo -e "Max image pixel: Allow to shrink attached/pasted image https://github.com/wekan/wekan/pull/2544" echo -e "Example:" echo -e "\t$ snap set $SNAP_NAME max-image-pixel='1024'" -echo -e "Disabled:" -echo -e "\t$ snap set $SNAP_NAME max-image-pixel=''" +echo -e "Disable:" +echo -e "\t$ snap unset $SNAP_NAME max-image-pixel" echo -e "\n" echo -e "Image compress ratio: Allow to shrink attached/pasted image https://github.com/wekan/wekan/pull/2544" echo -e "Example:" echo -e "\t$ snap set $SNAP_NAME image-compress-ratio='80'" -echo -e "Disabled:" -echo -e "\t$ snap set $SNAP_NAME image-compress-ratio=''" +echo -e "Disable:" +echo -e "\t$ snap unset $SNAP_NAME image-compress-ratio" echo -e "\n" echo -e "Allow to set attachment upload into specified server location. Create that directory first. https://github.com/wekan/wekan/pull/2603" echo -e "Example:" echo -e "\t$ snap set $SNAP_NAME attachments-store-path='/var/snap/wekan/common/attachments'" -echo -e "Disabled:" -echo -e "\t$ snap set $SNAP_NAME attachments-store-path=''" +echo -e "Disable:" +echo -e "\t$ snap unset $SNAP_NAME attachments-store-path" +echo -e "\n" +echo -e "NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE https://github.com/wekan/wekan/pull/2998" +echo -e "Number of days after a notification is read before we remove it. Default: 2." +echo -e "Example:" +echo -e "\t$ snap set $SNAP_NAME notification-tray-after-read-days-before-remove='4'" +echo -e "Restore default:" +echo -e "\t$ snap unset $SNAP_NAME notification-tray-after-read-days-before-remove" echo -e "\n" echo -e "BIGEVENTS DUE ETC NOTIFICATIONS https://github.com/wekan/wekan/pull/2541" echo -e "Big events pattern: Notify always due etc regardless of notification settings. Default: due, All: received|start|due|end, Disabled: NONE" @@ -120,34 +136,34 @@ echo -e "Notify due days, number less than 15 or negative number accepted, you c echo -e "To enable different Notify for Due Days on 2 days before, and on the event day " echo -e "\t$ snap set $SNAP_NAME notify-due-days-before-and-after='2,0'" echo -e "\t-Disable Notifying for Due Days:" -echo -e "\t$ snap set $SNAP_NAME notify-due-days-before-and-after=''" +echo -e "\t$ snap unset $SNAP_NAME notify-due-days-before-and-after" echo -e "\n" echo -e "Notify due at hour of day. Default every morning at 8am. Can be 0-23." echo -e "If env variable has parsing error, use default. Notification sent to watchers." echo -e "To enable different Notify Due At Hour Of Day than default 8:" echo -e "\t$ snap set $SNAP_NAME notify-due-at-hour-of-day='10'" echo -e "\t-To set back default 8 of Notify Due at Hour of Day:" -echo -e "\t$ snap set $SNAP_NAME notify-due-at-hour-of-day=''" +echo -e "\t$ snap unset $SNAP_NAME notify-due-at-hour-of-day" echo -e "\n" echo -e "To enable the Email Notification Timeout of wekan in ms, default 30000 (=30s):" echo -e "\t$ snap set $SNAP_NAME email-notification-timeout='10000'" -echo -e "\t-Disable the Email Notification Timeout of Wekan:" -echo -e "\t$ snap set $SNAP_NAME email-notification-timeout='30000'" +echo -e "\t-Restore default:" +echo -e "\t$ snap unset $SNAP_NAME email-notification-timeout" echo -e "\n" echo -e "To enable the CORS of wekan, to set Access-Control-Allow-Origin header:" echo -e "\t$ snap set $SNAP_NAME cors='*'" echo -e "\t-Disable the CORS:" -echo -e "\t$ snap set $SNAP_NAME cors=''" +echo -e "\t$ snap unset $SNAP_NAME cors" echo -e "\n" echo -e "To enable the Set Access-Control-Allow-Headers header. \"Authorization,Content-Type\" is required for cross-origin use of the API." echo -e "\t$ snap set $SNAP_NAME cors-allow-headers='Authorization,Content-Type'" echo -e "\t-Disable the Set Access-Control-Allow-Headers header. \"Authorization,Content-Type\" is required for cross-origin use of the API." -echo -e "\t$ snap set $SNAP_NAME cors-allow-headers=''" +echo -e "\t$ snap unset $SNAP_NAME cors-allow-headers" echo -e "\n" echo -e "To enable the Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations. Example: *" echo -e "\t$ snap set $SNAP_NAME cors-expose-headers='*'" echo -e "\t-Disable the Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations. Example: ''" -echo -e "\t$ snap set $SNAP_NAME cors-expose-headers=''" +echo -e "\t$ snap unset $SNAP_NAME cors-expose-headers" echo -e "\n" echo -e "Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside." echo -e "\t\t Setting this to false is not recommended, it also disables all other browser policy protections" @@ -161,19 +177,43 @@ echo -e "When browser policy is enabled, HTML code at this URL can have iframe t echo -e "To enable the Trusted URL of Wekan:" echo -e "\t$ snap set $SNAP_NAME trusted-url='https://example.com'" echo -e "\t-Disable the Trusted URL of Wekan:" -echo -e "\t$ snap set $SNAP_NAME trusted-url=''" +echo -e "\t$ snap unset $SNAP_NAME trusted-url" echo -e "\n" echo -e "What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId ." echo -e "To enable the Webhooks Attributes of Wekan:" echo -e "\t$ snap set $SNAP_NAME webhooks-attributes='cardId,listId,oldListId,boardId,comment,user,card,commentId'" echo -e "\t-Disable the Webhooks Attributes of Wekan to send all default ones:" -echo -e "\t$ snap set $SNAP_NAME webhooks-attributes=''" +echo -e "\t$ snap unset $SNAP_NAME webhooks-attributes" +echo -e "\n" +echo -e "OAuth2 Enabled." +echo -e "To enable the OAuth2 of Wekan:" +echo -e "\t$ snap set $SNAP_NAME oauth2-enabled='true'" +echo -e "\t-Disable the OAuth2 of Wekan:" +echo -e "\t$ snap unset $SNAP_NAME oauth2-enabled" +echo -e "\n" +echo -e "Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299" +echo -e "To enable the OAuth2 of Wekan:" +echo -e "\t$ snap set $SNAP_NAME oauth2-ca-cert='ABCD134'" +echo -e "\t-Disable the OAuth2 of Wekan:" +echo -e "\t$ snap unset $SNAP_NAME oauth2-ca-cert" +echo -e "\n" +echo -e "Enable OAuth2 Oracle on premise identity manager OIM. Default: false" +echo -e "To enable the OAuth2 Oracle OIM of Wekan:" +echo -e "\t$ snap set $SNAP_NAME oracle-oim-enabled='true'" +echo -e "\t-Disable the OAuth2 Oracle OIM of Wekan:" +echo -e "\t$ snap unset $SNAP_NAME oracle-oim-enabled" +echo -e "\n" +echo -e "OAuth2 ADFS Enabled. Also requires oauth2-enabled='true'" +echo -e "To enable the OAuth2 ADFS of Wekan:" +echo -e "\t$ snap set $SNAP_NAME oauth2-adfs-enabled='true'" +echo -e "\t-Disable the OAuth2 ADFS of Wekan:" +echo -e "\t$ snap unset $SNAP_NAME oauth2-adfs-enabled" echo -e "\n" echo -e "OAuth2 Client ID." echo -e "To enable the OAuth2 Client ID of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-client-id='54321abcde'" echo -e "\t-Disable the OAuth2 Client ID of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-client-id=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-client-id" echo -e "\n" echo -e "OAuth2 login style: popup or redirect. Default: redirect" echo -e "To enable the OAuth2 login style popup of Wekan:" @@ -185,67 +225,67 @@ echo -e "OAuth2 Secret." echo -e "To enable the OAuth2 Secret of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-secret='54321abcde'" echo -e "\t-Disable the OAuth2 Secret of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-secret=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-secret" echo -e "\n" echo -e "OAuth2 Server URL." echo -e "To enable the OAuth2 Server URL of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-server-url='https://chat.example.com'" echo -e "\t-Disable the OAuth2 Server URL of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-server-url=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-server-url" echo -e "\n" echo -e "OAuth2 Authorization Endpoint." echo -e "To enable the OAuth2 Authorization Endpoint of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-auth-endpoint='/oauth/authorize'" echo -e "\t-Disable the OAuth2 Authorization Endpoint of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-auth-endpoint=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-auth-endpoint" echo -e "\n" echo -e "OAuth2 Userinfo Endpoint." echo -e "To enable the OAuth2 Userinfo Endpoint of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-userinfo-endpoint='/oauth/authorize'" echo -e "\t-Disable the OAuth2 Userinfo Endpoint of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-userinfo-endpoint=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-userinfo-endpoint" echo -e "\n" echo -e "OAuth2 Token Endpoint." echo -e "To enable the OAuth2 Token Endpoint of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-token-endpoint='/oauth/token'" echo -e "\t-Disable the OAuth2 Token Endpoint of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-token-endpoint=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-token-endpoint" echo -e "\n" echo -e "OAuth2 ID Token Whitelist Fields." echo -e "To enable the OAuth2 ID Token Whitelist Fields of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-id-token-whitelist-fields=[]" echo -e "\t-Disable the OAuth2 ID Token Whitelist Fields of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-id-token-whitelist-fields=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-id-token-whitelist-fields" echo -e "\n" echo -e "OAuth2 Request Permissions." echo -e "To enable the OAuth2 Request Permissions of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-request-permissions=\"'openid profile email'\"" echo -e "\t-Disable the OAuth2 Request Permissions of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-request-permissions=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-request-permissions" echo -e "\n" echo -e "OAuth2 ID Mapping." echo -e "To enable the OAuth2 ID Mapping of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-id-map='username.uid'" echo -e "\t-Disable the OAuth2 ID Mapping of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-id-map=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-id-map" echo -e "\n" echo -e "OAuth2 Username Mapping." echo -e "To enable the OAuth2 Username Mapping of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-username-map='username'" echo -e "\t-Disable the OAuth2 Username Mapping of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-username-map=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-username-map" echo -e "\n" echo -e "OAuth2 Fullname Mapping." echo -e "To enable the OAuth2 Fullname Mapping of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-fullname-map='fullname'" echo -e "\t-Disable the OAuth2 Fullname Mapping of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-fullname-map=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-fullname-map" echo -e "\n" echo -e "OAuth2 Email Mapping." echo -e "To enable the OAuth2 Email Mapping of Wekan:" echo -e "\t$ snap set $SNAP_NAME oauth2-email-map='email'" echo -e "\t-Disable the OAuth2 Email Mapping of Wekan:" -echo -e "\t$ snap set $SNAP_NAME oauth2-email-map=''" +echo -e "\t$ snap unset $SNAP_NAME oauth2-email-map" echo -e "\n" echo -e "Ldap Enable." echo -e "To enable the ldap of Wekan:" @@ -456,6 +496,57 @@ echo -e "Default authentication method." echo -e "The default authentication method used if a user does not exist to create and authenticate. Method can be password or ldap." echo -e "\t$ snap set $SNAP_NAME default-authentication-method='ldap'" echo -e "\n" +echo -e "Enable or not password login Form" +echo -e "\t$ snap set $SNAP_NAME password-login-enabled='false'" +echo -e "\n" +echo -e "CAS Enabled. Default: false" +echo -e "\t$ snap set $SNAP_NAME cas-enabled='true'" +echo -e "\n" +echo -e "CAS Base URL." +echo -e "\t$ snap set $SNAP_NAME cas-base-url='https://cas.example.com/cas'" +echo -e "\n" +echo -e "CAS Login URL." +echo -e "\t$ snap set $SNAP_NAME cas-login-url='https://cas.example.com/login'" +echo -e "\n" +echo -e "CAS Validate URL." +echo -e "\t$ snap set $SNAP_NAME cas-validate-url='https://cas.example.com/cas/p3/serviceValidate'" +echo -e "\n" +echo -e "SAML Enabled. Default: false" +echo -e "\t$ snap set $SNAP_NAME saml-enabled='true'" +echo -e "\n" +echo -e "SAML Provider. openam or openidp." +echo -e "\t$ snap set $SNAP_NAME saml-provider='openam'" +echo -e "\n" +echo -e "SAML Entrypoint." +echo -e "\t$ snap set $SNAP_NAME saml-entrypoint=''" +echo -e "\n" +echo -e "SAML Issuer." +echo -e "\t$ snap set $SNAP_NAME saml-issuer=''" +echo -e "\n" +echo -e "SAML Cert." +echo -e "\t$ snap set $SNAP_NAME saml-cert=''" +echo -e "\n" +echo -e "SAML IDPS LO Redirect URL." +echo -e "\t$ snap set $SNAP_NAME saml-ispslo-redirecturl=''" +echo -e "\n" +echo -e "SAML Private Keyfile." +echo -e "\t$ snap set $SNAP_NAME saml-private-keyfile=''" +echo -e "\n" +echo -e "SAML Public Certfile." +echo -e "\t$ snap set $SNAP_NAME saml-public-certfile=''" +echo -e "\n" +echo -e "SAML Identifier Format." +echo -e "\t$ snap set $SNAP_NAME saml-identifier-format=''" +echo -e "\n" +echo -e "SAML Local Profile Match Attribute." +echo -e "\t$ snap set $SNAP_NAME saml-local-profile-match-attribute=''" +echo -e "\n" +echo -e "SAML Attributes." +echo -e "\t$ snap set $SNAP_NAME saml-attributes=''" +echo -e "\n" +echo -e "Wait spinner to use." +echo -e "\t$ snap set $SNAP_NAME wait-spinner='Bounce'" +echo -e "\n" # parse config file for supported settings keys echo -e "wekan supports settings keys" echo -e "values can be changed by calling\n$ snap set $SNAP_NAME =''" diff --git a/snapcraft.yaml b/snapcraft.yaml index 2b6b1ef96..5d2c2562c 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,6 +1,5 @@ name: wekan -version: 0 -version-script: git describe --tags | cut -c 2- +version: '5.38' summary: The open-source kanban description: | Wekan is an open-source and collaborative kanban board application. @@ -81,7 +80,7 @@ parts: wekan: source: . plugin: nodejs - node-engine: 12.16.1 + node-engine: 12.22.3 node-packages: - node-gyp - node-pre-gyp @@ -97,146 +96,65 @@ parts: - execstack - nodejs - npm +# - p7zip-full stage-packages: - libfontconfig1 override-build: | echo "Cleaning environment first" rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules - # Create the OpenAPI specification rm -rf .build - ## Use Meteor 1.8.x on Snap - #rm -rf .meteor - #mv .snap-meteor-1.8/.meteor . - #mv .snap-meteor-1.8/package.json . - #mv .snap-meteor-1.8/package-lock.json . - ## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those - #mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/ - #mv .snap-meteor-1.8/export.js models/ - #mv .snap-meteor-1.8/wekanCreator.js models/ - #mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js - #mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js - rm -rf .snap-meteor-1.8 - #mkdir -p .build/python - #cd .build/python - #git clone --depth 1 -b master https://github.com/Kronuz/esprima-python - #cd esprima-python - #python3 setup.py install - #cd ../../.. - #mkdir -p ./public/api - #python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml - # we temporary need api2html and mkdirp - #npm install -g api2html@0.3.0 - #npm install -g mkdirp - #api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml - #npm uninstall -g mkdirp - #npm uninstall -g api2html - # Node Fibers 100% CPU usage issue: - # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161 - # https://github.com/meteor/meteor/issues/9796#issuecomment-381676326 - # https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129 - # Also see beginning of wekan/server/authentication.js - # import Fiber from "fibers"; - # Fiber.poolSize = 1e9; - # OLD: Download node version 8.12.0 prerelease build => Official node 8.12.0 has been released - # Description at https://releases.wekan.team/node.txt - ##echo "375bd8db50b9c692c0bbba6e96d4114cd29bee3770f901c1ff2249d1038f1348 node" >> node-SHASUMS256.txt.asc - ##curl https://releases.wekan.team/node -o node - # Verify Fibers patched node authenticity - ##echo "Fibers 100% CPU issue patched node authenticity:" - ##grep node node-SHASUMS256.txt.asc | shasum -a 256 -c - - ##rm -f node-SHASUMS256.txt.asc - ##chmod +x node - ##mv node `which node` - # DOES NOT WORK: paxctl fix. - # Removed from build-packages: - paxctl - #echo "Applying paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303" - #paxctl -mC `which node` - #echo "Installing npm" - #curl -L https://www.npmjs.com/install.sh | sh echo "Installing meteor" curl https://install.meteor.com/ -o install_meteor.sh - #sed -i "s|RELEASE=.*|RELEASE=\"1.8.1-beta.0\"|g" install_meteor.sh chmod +x install_meteor.sh sh install_meteor.sh rm install_meteor.sh - # REPOS BELOW ARE INCLUDED TO WEKAN REPO - #if [ ! -d "packages" ]; then - # mkdir packages - #fi - #if [ ! -d "packages/kadira-flow-router" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router - # cd .. - #fi - #if [ ! -d "packages/meteor-useraccounts-core" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core - # sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' meteor-useraccounts-core/package.js - # cd .. - #fi - #if [ ! -d "packages/meteor-accounts-cas" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git meteor-accounts-cas - # cd .. - #fi - #if [ ! -d "packages/wekan-ldap" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git - # cd .. - #fi - #if [ ! -d "packages/wekan-scrollbar" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git - # cd .. - #fi - #if [ ! -d "packages/wekan_accounts-oidc" ]; then - # cd packages - # git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git - # mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc - # mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc - # rm -rf meteor-accounts-oidc - # cd .. - #fi - #if [ ! -d "packages/markdown" ]; then - # cd packages - # git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git - # cd .. - #fi rm -rf .build - meteor add standard-minifier-js --allow-superuser - meteor npm install --allow-superuser - meteor npm install --allow-superuser --save babel-runtime + chmod u+w *.json + npm install meteor build .build --directory --allow-superuser - cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js - #Removed binary version of bcrypt because of security vulnerability that is not fixed yet. - #https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac - #https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c - #cd .build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt - #rm -rf node_modules/bcrypt - #meteor npm install --save bcrypt - # Change from npm-bcrypt directory back to .build/bundle/programs/server directory. - #cd ../../../../ + # Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. + rm -rf .build/bundle/programs/web.browser.legacy # Change to directory .build/bundle/programs/server cd .build/bundle/programs/server + chmod u+w *.json npm install - npm install --allow-superuser --save babel-runtime - #meteor npm install --save bcrypt - # Change back to Wekan source directory cd ../../../.. + # Cleanup + cd .build/bundle + find . -type d -name '*-garbage*' | xargs rm -rf + find . -name '*phantom*' | xargs rm -rf + find . -name '.*.swp' | xargs rm -f + find . -name '*.swp' | xargs rm -f + cd ../.. + # Add fibers multi arch + #cd .build/bundle/programs/server/node_modules/fibers/bin + #curl https://releases.wekan.team/fibers-multi.7z -o fibers-multi.7z + #7z x fibers-multi.7z + #rm fibers-multi.7z + #cd ../../../../../../.. + # Copy to Snap cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/ cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/ rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan - rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs - rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp - rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp - rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp + #rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs + # Delete phantomjs that is in accounts-lockout + #rm -rf $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/lucasantoniassi_accounts-lockout/node_modules/phantomjs-prebuilt + # Delete temporary files + #rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp + #rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp + #rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp # Meteor 1.8.x additional .swp remove - rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp + #rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp + # Delete fibers for other archs + #rm -rf $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/fibers/bin/linux-ia32* + # ostrio tmp remove + #rm -rf $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/ostrio_files/node_modules/request-libcurl/.node_modules-garbage* organize: README: README.wekan prime: - -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp + - -lib/node_modules/weka* helpers: source: snap-src @@ -244,8 +162,15 @@ parts: caddy: plugin: dump - source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off - source-type: tar + ## Caddy v1 is not developed anymore. TODO: Sometime migrate to Caddy v2. + ## https://caddy.community/t/caddyfile-v1-adapter/9129 + ## https://github.com/caddyserver/caddy/tree/v1 + #source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off + #source-type: tar + # Using last working binary that was downloaded from above URL to Wekan Snap, + # and .txt files from https://github.com/caddyserver/caddy/tree/v1/dist + source: https://releases.wekan.team/caddy/caddy-v1-linux-amd64.7z + source-type: 7z organize: caddy: bin/caddy CHANGES.txt: CADDY_CHANGES.txt diff --git a/stacksmith/user-scripts/build.sh b/stacksmith/user-scripts/build.sh index 00448514c..24426f77d 100755 --- a/stacksmith/user-scripts/build.sh +++ b/stacksmith/user-scripts/build.sh @@ -2,7 +2,7 @@ set -euxo pipefail BUILD_DEPS="bsdtar gnupg wget curl bzip2 python git ca-certificates perl-Digest-SHA" -NODE_VERSION=v12.16.1 +NODE_VERSION=v12.22.3 #METEOR_RELEASE=1.6.0.1 - for Stacksmith, meteor-1.8 branch that could have METEOR@1.8.1-beta.8 or newer USE_EDGE=false METEOR_EDGE=1.5-beta.17 @@ -75,6 +75,8 @@ sudo -u wekan ${meteor} build --directory /home/wekan/app_build sudo cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js sudo chown wekan:wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js sudo rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs +# 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 cd /home/wekan/app_build/bundle/programs/server/ sudo npm install sudo chown -R wekan:wekan ./node_modules diff --git a/start-wekan.bat b/start-wekan.bat old mode 100755 new mode 100644 index bb5b883c1..64eeb405d --- a/start-wekan.bat +++ b/start-wekan.bat @@ -1,9 +1,4 @@ -REM ------------------------------------------------------------ - -REM NOTE: THIS .BAT DOES NOT WORK !! -REM Use instead this webpage instructions to build on Windows: -REM https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows -REM Please add fix PRs, like config of MongoDB etc. +@ECHO OFF REM ------------------------------------------------------------ @@ -12,11 +7,16 @@ REM SET DEBUG=true REM ------------------------------------------------------------ +SET ROOT_URL=http://localhost +SET PORT=80 SET MONGO_URL=mongodb://127.0.0.1:27017/wekan -SET ROOT_URL=http://127.0.0.1:2000/ -SET MAIL_URL=smtp://user:pass@mailserver.example.com:25/ -SET MAIL_FROM=admin@example.com -SET PORT=2000 + +REM # https://github.com/wekan/wekan/wiki/Troubleshooting-Mail +REM SET MAIL_URL=smtps://username:password@email-smtp.eu-west-1.amazonaws.com:587/ +REM SET MAIL_FROM="Wekan Boards " + +REM # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== +REM SET RESULTS_PER_PAGE=20 REM # If you disable Wekan API with false, Export Board does not work. SET WITH_API=true @@ -25,11 +25,6 @@ REM # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== REM # https://github.com/wekan/wekan/pull/2560 SET RICHER_CARD_COMMENT_EDITOR=false -REM # ==== MOUSE SCROLL ==== -REM # https://github.com/wekan/wekan/issues/2949 -SET SCROLLINERTIA=0 -SET SCROLLAMOUNT=auto - REM # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== SET CARD_OPENED_WEBHOOK_ENABLED=false @@ -48,6 +43,11 @@ REM SET ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 REM SET ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 REM SET ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 +REM # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== +REM # Number of days after a notification is read before we remove it. +REM # Default: 2 +REM SET NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + REM # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== REM # https://github.com/wekan/wekan/pull/2541 REM # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", @@ -117,11 +117,22 @@ REM SET WEBHOOKS_ATTRIBUTES= REM ------------------------------------------------------------ +REM # OAUTH2 ORACLE on premise identity manager OIM +REM SET ORACLE_OIM_ENABLED=true + +REM ------------------------------------------------------------ + REM # Enable the OAuth2 connection REM # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 REM # example: OAUTH2_ENABLED=true REM SET OAUTH2_ENABLED=false +REM # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299 +REM SET OAUTH2_CA_CERT=ABCD1234 + +REM # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. +REM SET OAUTH2_ADFS_ENABLED=false + REM # OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345 REM # example: OAUTH2_CLIENT_ID=abcde12345 REM SET OAUTH2_CLIENT_ID= @@ -358,6 +369,13 @@ REM SET LDAP_SYNC_ADMIN_STATUS=true REM # Comma separated list of admin group names to sync. REM SET LDAP_SYNC_ADMIN_GROUPS=group1,group2 +REM ------------------------------------------------ + +REM # Enable/Disable password login form. +REM SET PASSWORD_LOGIN_ENABLED=true + +REM ------------------------------------------------ + REM # Login to LDAP automatically with HTTP header. REM # In below example for siteminder, at right side of = is header name. REM SET HEADER_LOGIN_ID=HEADERUID @@ -383,6 +401,24 @@ REM # LOGOUT_ON_MINUTES : The number of minutes REM # example : LOGOUT_ON_MINUTES=55 REM SET LOGOUT_ON_MINUTES= -cd .build\bundle +REM SET CAS_ENABLED=true +REM SET CAS_BASE_URL=https://cas.example.com/cas +REM SET CAS_LOGIN_URL=https://cas.example.com/login +REM SET CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + +REM SET SAML_ENABLED=true +REM SET SAML_PROVIDER= +REM SET SAML_ENTRYPOINT= +REM SET SAML_ISSUER= +REM SET SAML_CERT= +REM SET SAML_IDPSLO_REDIRECTURL= +REM SET SAML_PRIVATE_KEYFILE= +REM SET SAML_PUBLIC_CERTFILE= +REM SET SAML_IDENTIFIER_FORMAT= +REM SET SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= +REM SET SAML_ATTRIBUTES= + +REM # Wait spinner to use +REM SET WAIT_SPINNER=Bounce + node main.js -cd ..\.. diff --git a/start-wekan.sh b/start-wekan.sh index bb9249a6c..a4eeb31ae 100755 --- a/start-wekan.sh +++ b/start-wekan.sh @@ -23,6 +23,9 @@ # This is local port where Wekan Node.js runs, same as below on Caddyfile settings. export PORT=2000 #--------------------------------------------- + # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== + #export RESULTS_PER_PAGE=20 + #--------------------------------------------- # Wekan Export Board works when WITH_API=true. # If you disable Wekan API with false, Export Board does not work. export WITH_API='true' @@ -41,11 +44,6 @@ # https://github.com/wekan/wekan/pull/2560 export RICHER_CARD_COMMENT_EDITOR=false #--------------------------------------------------------------- - # ==== MOUSE SCROLL ==== - # https://github.com/wekan/wekan/issues/2949 - export SCROLLINERTIA=0 - export SCROLLAMOUNT=auto - #--------------------------------------------------------------- # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== export CARD_OPENED_WEBHOOK_ENABLED=false #--------------------------------------------------------------- @@ -54,6 +52,11 @@ #export MAX_IMAGE_PIXEL=1024 #export IMAGE_COMPRESS_RATIO=80 #--------------------------------------------------------------- + # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== + # Number of days after a notification is read before we remove it. + # Default: 2 + #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + #--------------------------------------------------------------- # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== # https://github.com/wekan/wekan/pull/2541 # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", @@ -120,6 +123,9 @@ # Example: export WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId export WEBHOOKS_ATTRIBUTES='' #--------------------------------------------- + # OAUTH2 ORACLE on premise identity manager OIM + #export ORACLE_OIM_ENABLED=true + #--------------------------------------------- # ==== OAUTH2 AZURE ==== # https://github.com/wekan/wekan/wiki/Azure # 1) Register the application with Azure. Make sure you capture @@ -127,6 +133,10 @@ # 2) Configure the environment variables. This differs slightly # by installation type, but make sure you have the following: #export OAUTH2_ENABLED=true + # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299 + #export OAUTH2_CA_CERT=ABCD1234 + # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. + #export OAUTH2_ADFS_ENABLED=false # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 # OAuth2 login style: popup or redirect. #export OAUTH2_LOGIN_STYLE=redirect @@ -357,7 +367,30 @@ # LOGOUT_ON_MINUTES : The number of minutes # example : LOGOUT_ON_MINUTES=55 #export LOGOUT_ON_MINUTES= - + #--------------------------------------------------------------------- + # PASSWORD_LOGIN_ENABLED : Enable or not the password login form. + #export PASSWORD_LOGIN_ENABLED=true + #--------------------------------------------------------------------- + #export CAS_ENABLED=true + #export CAS_BASE_URL=https://cas.example.com/cas + #export CAS_LOGIN_URL=https://cas.example.com/login + #export CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + #--------------------------------------------------------------------- + #export SAML_ENABLED=true + #export SAML_PROVIDER= + #export SAML_ENTRYPOINT= + #export SAML_ISSUER= + #export SAML_CERT= + #export SAML_IDPSLO_REDIRECTURL= + #export SAML_PRIVATE_KEYFILE= + #export SAML_PUBLIC_CERTFILE= + #export SAML_IDENTIFIER_FORMAT= + #export SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= + #export SAML_ATTRIBUTES= + #--------------------------------------------------------------------- + # Wait spinner to use + #export WAIT_SPINNER=Bounce + #--------------------------------------------------------------------- node main.js # & >> ../../wekan.log cd ../.. diff --git a/support-at-wekan.team_pgp-publickey.asc b/support-at-wekan.team_pgp-publickey.asc new file mode 100644 index 000000000..e59ce9034 --- /dev/null +++ b/support-at-wekan.team_pgp-publickey.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: OpenPGP.js v4.10.8 +Comment: https://openpgpjs.org + +xjMEX3HbABYJKwYBBAHaRw8BAQdAQ2UBWCK1H3/z7gMgwVudRL+NlwJVCPw+ +Vsp9PGSLh9jNJ3N1cHBvcnRAd2VrYW4udGVhbSA8c3VwcG9ydEB3ZWthbi50 +ZWFtPsKPBBAWCgAgBQJfcdsABgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEA +IQkQRkG8ZpPn6f4WIQRI5gPihwJLiXfveDBGQbxmk+fp/kCXAQD/JXsgVq4d +9sKmYDGHpCaIfsO//6cmiCGz3Mf5SDc0ygD/WpJO31Fyu6pfr3nWe4n50H93 +lXXz937+K1bB9rfqugDOOARfcdsAEgorBgEEAZdVAQUBAQdALydXPub/n7hx +8qYjZa2tzBvcz5KkdnIxOoB+vaQZFQwDAQgHwngEGBYIAAkFAl9x2wACGwwA +IQkQRkG8ZpPn6f4WIQRI5gPihwJLiXfveDBGQbxmk+fp/uyXAQCRLPksHCJ6 +RTl7HrtSS9lkeOmh32u+Rnjijn970PYIIQEAiGgXoJGBTzyVil9aPeqfWFK+ +0hvTsnNY3JT3K84OmQQ= +=SNk3 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test-wekan.sh b/test-wekan.sh new file mode 100755 index 000000000..f98d7191d --- /dev/null +++ b/test-wekan.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + + +set -e + +# ------------------------------------------ +# +# Variable declarations +# +# ------------------------------------------ + +PROJECT_ROOT=$(pwd) +PORT=4040 +RUN_ONCE='--once' +VERBOSE_MODE=0 +WATCH_MODE=0 +COVERAGE=0 + +# ------------------------------------------ +# +# Read args from script call +# +# ------------------------------------------ + +while getopts "vcw" opt; do + case $opt in + v) + VERBOSE_MODE=1 + ;; + c) + COVERAGE=1 + ;; + w) + WATCH_MODE=1 + RUN_ONCE='' + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + esac +done + +# ------------------------------------------ +# +# Print variables on verbose mode +# +# ------------------------------------------ + +if [ "$VERBOSE_MODE" -eq "1" ]; +then + PROJECT_NAME=`basename "$PROJECT_ROOT"` + echo "=> Test $PROJECT_NAME" + echo "=> Path: [${PROJECT_ROOT}]" + echo "=> Port: [${PORT}]" + echo "=> Watch mode: [${WATCH_MODE}] ${RUN_ONCE}" + echo "=> COVERAGE: [${COVERAGE}]" +fi + + +if [ "$WATCH_MODE" -eq "0" ]; +then + # --------------------------------------------------------------- + # in cli mode we use a headless browser to include client tests + # and we activate the coverage reporting functionality + # --------------------------------------------------------------- + BABEL_ENV=COVERAGE \ + TEST_BROWSER_DRIVER=puppeteer \ + TEST_SERVER=1 \ + TEST_CLIENT=1 \ + COVERAGE=${COVERAGE} \ + COVERAGE_OUT_HTML=1 \ + COVERAGE_OUT_LCOVONLY=1 \ + COVERAGE_OUT_TEXT_SUMMARY=1 \ + COVERAGE_OUT_JSON_SUMMARY=1 \ + COVERAGE_APP_FOLDER=$PWD/ \ + COVERAGE_VERBOSE_MODE=${VERBOSE_MODE} \ + meteor test \ + --exclude-archs=web.browser.legacy,web.cordova \ + --driver-package=meteortesting:mocha \ + --settings=settings.json \ + --port=${PORT} \ + --once + cat ./.coverage/summary.txt + else + # --------------------------------------------------------------- + # in watch mode we neither use a browser driver, nor coverage + # se we speed up the test reload in the development phase + # --------------------------------------------------------------- + TEST_BROWSER_DRIVER=puppeteer \ + TEST_SERVER=1 \ + TEST_CLIENT=1 \ + meteor test \ + --exclude-archs=web.browser.legacy,web.cordova \ + --driver-package=meteortesting:mocha \ + --settings=settings.json \ + --port=${PORT} +fi diff --git a/tests/main.js b/tests/main.js new file mode 100644 index 000000000..648be68c2 --- /dev/null +++ b/tests/main.js @@ -0,0 +1,30 @@ +/* eslint-env mocha */ + +// This is the main test file from which all tests can be imported top-down, +// creating a directed sequence for tests that sums up to our test-suite. +// +// You propably want to start with low-level code and follow up to higher-level +// code, like for example: +// +// infrastructure +// utils / helpers +// contexts +// api +// components +// ui + +// If you want to run tests on both, server AND client, simply import them as +// they are. However, if you want to restict tests to server-only or client-only +// you need to wrap them inside a new describe-block + +if (Meteor.isServer) { + describe('server', function() { + import '../server/lib/tests/utils.tests'; + }); +} + +if (Meteor.isClient) { + describe('lib', function() { + import '../client/lib/tests'; + }); +} diff --git a/torodb-postgresql/docker-compose.yml b/torodb-postgresql/docker-compose.yml index d741d1a14..29b7effc7 100644 --- a/torodb-postgresql/docker-compose.yml +++ b/torodb-postgresql/docker-compose.yml @@ -9,15 +9,16 @@ version: '2' # https://github.com/wekan/wekan/wiki/Forgot-Password #--------------------------------------------------------------------------------------------------------- # ==== Upgrading Wekan to new version ===== +# NOTE: MongoDB has changed from 3.x to 4.x, in that case you need backup/restore with --noIndexRestore +# see https://github.com/wekan/wekan/wiki/Backup # 1) Stop Wekan: # docker-compose stop -# 2) Download new version: -# docker-compose pull wekan -# 3) If you have more networks for VPN etc as described at bottom of -# this config, download for them too: -# docker-compose pull wekan2 +# 2) Remove old Wekan app (wekan-app only, not that wekan-db container that has all your data) +# docker rm wekan-app +# 3) Get newest docker-compose.yml from https://github.com/wekan/wekan to have correct image, +# for example: "image: quay.io/wekan/wekan" or version tag "image: quay.io/wekan/wekan:v4.52" # 4) Start Wekan: -# docker-compose start +# docker-compose up -d #---------------------------------------------------------------------------------- # ==== OPTIONAL: DEDICATED DOCKER USER ==== # 1) Optionally create a dedicated user for Wekan, for example: @@ -84,17 +85,21 @@ version: '2' # # 11) Start wekan # docker start wekan-app #------------------------------------------------------------------------- +# 2020-12-03: +# - base images copied from Docker Hub to Quay to avoid Docker Hub rate limits, +# from: torodb/stampede:1.0.0, postgres:9.6, mongo:3.2 +#------------------------------------------------------------------------- services: torodb-stampede: - image: torodb/stampede:latest + image: quay.io/wekan/torodb-stampede:1.0.0 networks: - wekan-tier links: - postgres - mongodb environment: - - POSTGRES_PASSWORD + - POSTGRES_PASSWORD=wekan - TORODB_SETUP=true - TORODB_SYNC_SOURCE=mongodb:27017 - TORODB_BACKEND_HOST=postgres @@ -104,11 +109,11 @@ services: - TORODB_BACKEND_PASSWORD=wekan - DEBUG postgres: - image: postgres:9.6 + image: quay.io/wekan/postgres:9.6 networks: - wekan-tier environment: - - POSTGRES_PASSWORD + - POSTGRES_PASSWORD=wekan ports: - "5432:5432" mongodb: @@ -173,7 +178,7 @@ services: # that if you're using more than 1 instance of Wekan # (or any MeteorJS based tool) you're supposed to set # MONGO_OPLOG_URL as an environment variable. - # Without setting it, Meteor will perform a pull-and-diff + # Without setting it, Meteor will perform a poll-and-diff # update of it's dataset. With it, Meteor will update from # the OPLOG. See here # https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908 @@ -205,6 +210,9 @@ services: # There is Feature Request: Logging date and time of all activity with summary reports, # and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598 #--------------------------------------------------------------- + # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== + #- RESULTS_PER_PAGE=20 + #--------------------------------------------------------------- # ==== WEKAN API AND EXPORT BOARD ==== # Wekan Export Board works when WITH_API=true. # https://github.com/wekan/wekan/wiki/REST-API @@ -230,11 +238,6 @@ services: # https://github.com/wekan/wekan/pull/2560 - RICHER_CARD_COMMENT_EDITOR=false #--------------------------------------------------------------- - # ==== MOUSE SCROLL ==== - # https://github.com/wekan/wekan/issues/2949 - - SCROLLINERTIA=0 - - SCROLLAMOUNT=auto - #--------------------------------------------------------------- # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== # https://github.com/wekan/wekan/issues/2518 - CARD_OPENED_WEBHOOK_ENABLED=false @@ -244,6 +247,11 @@ services: #-MAX_IMAGE_PIXEL=1024 #-IMAGE_COMPRESS_RATIO=80 #--------------------------------------------------------------- + # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== + # Number of days after a notification is read before we remove it. + # Default: 2 + #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + #--------------------------------------------------------------- # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== # https://github.com/wekan/wekan/pull/2541 # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", @@ -309,12 +317,19 @@ services: # example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId #- WEBHOOKS_ATTRIBUTES= #----------------------------------------------------------------- + # ==== OAUTH2 ORACLE on premise identity manager OIM ==== + #- ORACLE_OIM_ENABLED=true + #----------------------------------------------------------------- # ==== OAUTH2 ONLY WITH OIDC AND DOORKEEPER AS INDENTITY PROVIDER # https://github.com/wekan/wekan/issues/1874 # https://github.com/wekan/wekan/wiki/OAuth2 # Enable the OAuth2 connection # example: OAUTH2_ENABLED=true #- OAUTH2_ENABLED=false + # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299 + #- OAUTH2_CA_CERT=ABCD1234 + # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. + #- OAUTH2_ADFS_ENABLED=false # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 # OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345 # example: OAUTH2_CLIENT_ID=abcde12345 @@ -522,7 +537,31 @@ services: # LOGOUT_ON_MINUTES : The number of minutes # example : LOGOUT_ON_MINUTES=55 #- LOGOUT_ON_MINUTES= + #--------------------------------------------------------------------- + # PASSWORD_LOGIN_ENABLED : Enable or not the password login form. + # example: PASSWORD_LOGIN_ENABLED=false + # - PASSWORD_LOGIN_ENABLED #------------------------------------------------------------------- + #- CAS_ENABLED=true + #- CAS_BASE_URL=https://cas.example.com/cas + #- CAS_LOGIN_URL=https://cas.example.com/login + #- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + #--------------------------------------------------------------------- + #- SAML_ENABLED=true + #- SAML_PROVIDER= + #- SAML_ENTRYPOINT= + #- SAML_ISSUER= + #- SAML_CERT= + #- SAML_IDPSLO_REDIRECTURL= + #- SAML_PRIVATE_KEYFILE= + #- SAML_PUBLIC_CERTFILE= + #- SAML_IDENTIFIER_FORMAT= + #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= + #- SAML_ATTRIBUTES= + #--------------------------------------------------------------------- + # Wait spinner to use + #- WAIT_SPINNER=Bounce + #--------------------------------------------------------------------- depends_on: - mongodb diff --git a/trello/trello-project100.json b/trello/trello-project100.json new file mode 100644 index 000000000..cd298a5a0 --- /dev/null +++ b/trello/trello-project100.json @@ -0,0 +1,2383 @@ +{ + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "desc": "", + "descData": null, + "closed": false, + "idOrganization": "5fcb9620f11c51113570789c", + "shortLink": "CsjMiavu", + "powerUps": [], + "dateLastActivity": "2021-01-19T09:18:13.626Z", + "idTags": [], + "datePluginDisable": null, + "creationMethod": null, + "idBoardSource": null, + "idMemberCreator": "573c6868cbda1f0332684fb0", + "idEnterprise": null, + "pinned": false, + "starred": false, + "url": "https://trello.com/b/CsjMiavu/project-100", + "shortUrl": "https://trello.com/b/CsjMiavu", + "ixUpdate": "73", + "limits": { + "attachments": { + "perBoard": { + "status": "ok", + "disableAt": 36000, + "warnAt": 32400 + }, + "perCard": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + } + }, + "boards": { + "totalMembersPerBoard": { + "status": "ok", + "disableAt": 1600, + "warnAt": 1440 + } + }, + "cards": { + "openPerBoard": { + "status": "ok", + "disableAt": 5000, + "warnAt": 4500 + }, + "openPerList": { + "status": "ok", + "disableAt": 5000, + "warnAt": 4500 + }, + "totalPerBoard": { + "status": "ok", + "disableAt": 2000000, + "warnAt": 1800000 + }, + "totalPerList": { + "status": "ok", + "disableAt": 1000000, + "warnAt": 900000 + } + }, + "checklists": { + "perBoard": { + "status": "ok", + "disableAt": 2000000, + "warnAt": 1800000 + }, + "perCard": { + "status": "ok", + "disableAt": 500, + "warnAt": 450 + } + }, + "checkItems": { + "perChecklist": { + "status": "ok", + "disableAt": 200, + "warnAt": 180 + } + }, + "customFields": { + "perBoard": { + "status": "ok", + "disableAt": 50, + "warnAt": 45 + } + }, + "customFieldOptions": { + "perField": { + "status": "ok", + "disableAt": 50, + "warnAt": 45 + } + }, + "labels": { + "perBoard": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + } + }, + "lists": { + "openPerBoard": { + "status": "ok", + "disableAt": 500, + "warnAt": 450 + }, + "totalPerBoard": { + "status": "ok", + "disableAt": 3000, + "warnAt": 2700 + } + }, + "stickers": { + "perCard": { + "status": "ok", + "disableAt": 70, + "warnAt": 63 + } + }, + "reactions": { + "perAction": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + }, + "uniquePerAction": { + "status": "ok", + "disableAt": 17, + "warnAt": 16 + } + } + }, + "enterpriseOwned": false, + "subscribed": false, + "templateGallery": null, + "premiumFeatures": [], + "dateLastView": "2021-01-19T09:18:13.641Z", + "labelNames": { + "green": "", + "yellow": "", + "orange": "", + "red": "", + "purple": "", + "blue": "", + "sky": "", + "lime": "", + "pink": "", + "black": "" + }, + "prefs": { + "permissionLevel": "org", + "hideVotes": false, + "voting": "disabled", + "comments": "members", + "invitations": "members", + "selfJoin": true, + "cardCovers": true, + "isTemplate": false, + "cardAging": "regular", + "calendarFeedEnabled": false, + "background": "5fc6b57550bfd588d8a2a598", + "backgroundImage": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/original/40b6a656858811e3bf7ca722a8ff163a/photo-1606773754386-1a6fb741841a", + "backgroundImageScaled": [ + { + "width": 140, + "height": 93, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/140x93/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 256, + "height": 171, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/256x171/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 480, + "height": 320, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/480x320/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 960, + "height": 640, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/960x640/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 1024, + "height": 683, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1024x683/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 2048, + "height": 1366, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2048x1366/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 1280, + "height": 854, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1280x854/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 1920, + "height": 1280, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1920x1280/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 2400, + "height": 1600, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2400x1600/6c867e7205be5e150f4cb19b8e02b6b5/photo-1606773754386-1a6fb741841a.jpg" + }, + { + "width": 2560, + "height": 1707, + "url": "https://trello-backgrounds.s3.amazonaws.com/SharedBackground/original/40b6a656858811e3bf7ca722a8ff163a/photo-1606773754386-1a6fb741841a" + } + ], + "backgroundTile": false, + "backgroundBrightness": "dark", + "backgroundBottomColor": "#151b16", + "backgroundTopColor": "#ebe1e0", + "canBePublic": true, + "canBeEnterprise": true, + "canBeOrg": true, + "canBePrivate": true, + "canInvite": true + }, + "actions": [ + { + "id": "6006a3d58801924a07258fe2", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a316f6284c2a9d56455f", + "name": "Logical Value", + "type": "checkbox" + }, + "customFieldItem": { + "id": "6006a3d58801924a07258fe1", + "value": { + "checked": "true" + }, + "idCustomField": "6006a316f6284c2a9d56455f", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:18:13.618Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "TU", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a3cef37f842d6d0e65d6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a36ff2036a5d85479f8b", + "name": "Text Value", + "type": "text" + }, + "customFieldItem": { + "id": "6006a3cef37f842d6d0e65d5", + "value": { + "text": "Hello" + }, + "idCustomField": "6006a36ff2036a5d85479f8b", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:18:06.094Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a3c9d21c1040d78eb552", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a322ac11ff0bebf5e867", + "name": "Date Value", + "type": "date" + }, + "customFieldItem": { + "id": "6006a3c9d21c1040d78eb551", + "value": { + "date": "2021-01-07T10:00:00.000Z" + }, + "idCustomField": "6006a322ac11ff0bebf5e867", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:18:01.869Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a39e35b0703fd198d92f", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a36ff2036a5d85479f8b", + "name": "Text Value", + "type": "text" + }, + "customFieldItem": { + "id": "6006a39e35b0703fd198d92e", + "value": { + "text": "Test text" + }, + "idCustomField": "6006a36ff2036a5d85479f8b", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:17:18.745Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a39120b4fb4013812fee", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a365e69d965129190ad5", + "name": "Number Value", + "type": "number" + }, + "customFieldItem": { + "id": "6006a39120b4fb4013812fed", + "value": { + "number": "100.32" + }, + "idCustomField": "6006a365e69d965129190ad5", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:17:05.608Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a38aa44102505b0df554", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "idValue": null + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "name": "Dropdown", + "type": "list" + }, + "customFieldItem": { + "id": "6006a38aa44102505b0df553", + "idValue": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:16:58.818Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a387be5e6f36b610d695", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a322ac11ff0bebf5e867", + "name": "Date Value", + "type": "date" + }, + "customFieldItem": { + "id": "6006a387be5e6f36b610d694", + "value": { + "date": "2021-01-28T10:00:00.000Z" + }, + "idCustomField": "6006a322ac11ff0bebf5e867", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:16:55.616Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a37bfaecc82b4f05b650", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "value": null + }, + "customField": { + "id": "6006a316f6284c2a9d56455f", + "name": "Logical Value", + "type": "checkbox" + }, + "customFieldItem": { + "id": "6006a37bfaecc82b4f05b64f", + "value": { + "checked": "true" + }, + "idCustomField": "6006a316f6284c2a9d56455f", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + } + }, + "type": "updateCustomFieldItem", + "date": "2021-01-19T09:16:43.615Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a36ff2036a5d85479f8c", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "customField": { + "id": "6006a36ff2036a5d85479f8b", + "name": "Text Value" + } + }, + "type": "createCustomField", + "date": "2021-01-19T09:16:31.477Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a365e69d965129190ad6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "customField": { + "id": "6006a365e69d965129190ad5", + "name": "Number Value" + } + }, + "type": "createCustomField", + "date": "2021-01-19T09:16:21.092Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a349e4610647a72e5857", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "display": { + "cardFront": false + } + }, + "customField": { + "display": { + "cardFront": true + }, + "id": "6006a33439b34a6d32962e40", + "name": "Dropdown" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:53.574Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a348943ff87758878fc6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "display": { + "cardFront": true + } + }, + "customField": { + "display": { + "cardFront": false + }, + "id": "6006a33439b34a6d32962e40", + "name": "Dropdown" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:52.391Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a342211c877049a0d096", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + }, + { + "id": "6006a341d1195d325a5401d7", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Low" + }, + "color": "none", + "pos": 49152 + } + ] + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "dc10651a093834befed19308dd27fbdb6f552375b4e20e9b1e49fbd5dce97c2d", + "display": { + "cardFront": true + }, + "name": "Dropdown", + "pos": 49152, + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + }, + { + "id": "6006a341d1195d325a5401d7", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Low" + }, + "color": "none", + "pos": 49152 + }, + { + "id": "6006a342211c877049a0d095", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "High" + }, + "color": "none", + "pos": 65536 + } + ], + "type": "list", + "isSuggestedField": false + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:46.956Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a341d1195d325a5401d8", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + } + ] + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "dc10651a093834befed19308dd27fbdb6f552375b4e20e9b1e49fbd5dce97c2d", + "display": { + "cardFront": true + }, + "name": "Dropdown", + "pos": 49152, + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + }, + { + "id": "6006a341d1195d325a5401d7", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Low" + }, + "color": "none", + "pos": 49152 + } + ], + "type": "list", + "isSuggestedField": false + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:45.472Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a33fecf9373dd454b423", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + } + ] + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "dc10651a093834befed19308dd27fbdb6f552375b4e20e9b1e49fbd5dce97c2d", + "display": { + "cardFront": true + }, + "name": "Dropdown", + "pos": 49152, + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + } + ], + "type": "list", + "isSuggestedField": false + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:43.393Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a33d53a30716478adc00", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "options": [] + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "dc10651a093834befed19308dd27fbdb6f552375b4e20e9b1e49fbd5dce97c2d", + "display": { + "cardFront": true + }, + "name": "Dropdown", + "pos": 49152, + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + } + ], + "type": "list", + "isSuggestedField": false + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "updateCustomField", + "date": "2021-01-19T09:15:41.440Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a33439b34a6d32962e41", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "customField": { + "id": "6006a33439b34a6d32962e40", + "name": "Dropdown" + } + }, + "type": "createCustomField", + "date": "2021-01-19T09:15:32.562Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a322ac11ff0bebf5e868", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "customField": { + "id": "6006a322ac11ff0bebf5e867", + "name": "Date Value" + } + }, + "type": "createCustomField", + "date": "2021-01-19T09:15:14.104Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a316f6284c2a9d564560", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "customField": { + "id": "6006a316f6284c2a9d56455f", + "name": "Logical Value" + } + }, + "type": "createCustomField", + "date": "2021-01-19T09:15:02.294Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "6006a2fafbdba78888ba805a", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "plugin": { + "id": "56d5e249a98895a9797bebb9", + "idOrganizationOwner": "5a999cd36066c584c097f193", + "author": "Trello Inc", + "capabilities": ["callback", "show-settings"], + "capabilitiesOptions": [], + "categories": ["board-utilities", "it-project-management"], + "iframeConnectorUrl": "https://card-fields.trello.services/index.html?ver=11a6113a0b14", + "name": "Custom Fields", + "privacyUrl": "https://trello.com/privacy", + "public": true, + "moderatedState": null, + "supportEmail": "https://trello.com/contact#/", + "url": "https://card-fields.trello.services/manifest.json", + "tags": ["essential", "staff-pick", "made-by-trello"], + "heroImageUrl": { + "_id": "5b562abf4a1dc08e2cad540f", + "@1x": "https://plugin.trello.services/images/custom-fields.png", + "@2x": "https://plugin.trello.services/images/custom-fields@2x.png" + }, + "isCompliantWithPrivacyStandards": null, + "usageBrackets": { + "boards": 1000000 + }, + "claimedDomains": [], + "icon": { + "url": "https://card-fields.trello.services/images/custom-fields-circular.svg" + }, + "listing": { + "name": "Custom Fields", + "locale": "en-US", + "description": "Custom fields.\n\nCustomize cards for the way you work.\n\nThe Custom Fields Power-Up enables you to add more info to your Trello cards that's specific to the way you get things done.\n\n- **Custom text fields** – Add a space to enter names, email addresses, or other information that’s crucial for your cards to have.\n- **Numbers** – Enable your cards to show story points, number of guests, order numbers, or any other number.\n- **Checkbox** – Need to know when something is approved or if there are special considerations? Add a checkbox.\n- **Dates** – Milestones, checkpoints, order dates—make sure crucial dates are shown on your cards.\n- **Dropdown lists** – When you need to select from multiple options like status or SKUs, a dropdown is your best bet.\n\nThe info you add to your cards with Custom Fields will show up on the front of your card, making it easy to get at-a-glance info on your board. You can export Custom Field data in TUON and CSV formats. *Note: CSV exports are only available for Business Class and Enterprise teams.*\n\n![screenshot](https://card-fields.trello.services/images/custom-fields-1.jpg)\n![screenshot](https://card-fields.trello.services/images/custom-fields-2.jpg)\n![screenshot](https://card-fields.trello.services/images/custom-fields-3.jpg)", + "overview": "Add custom fields like text, numbers, checkboxes, dates, and dropdown lists to cards." + } + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "enablePlugin", + "date": "2021-01-19T09:14:34.018Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec69a7cf444a408253a6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "idMember": "573c6868cbda1f0332684fb0", + "card": { + "id": "5fcbec28c680d2608374ce89", + "name": "Review code", + "idShort": 1, + "shortLink": "SMc4sR3Q" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "member": { + "id": "573c6868cbda1f0332684fb0", + "name": "Test User" + } + }, + "type": "addMemberToCard", + "date": "2020-12-05T20:24:09.138Z", + "appCreator": null, + "limits": {}, + "member": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + }, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec5e9262090da8042fc6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "idMember": "573c6868cbda1f0332684fb0", + "card": { + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "member": { + "id": "573c6868cbda1f0332684fb0", + "name": "Test User" + } + }, + "type": "addMemberToCard", + "date": "2020-12-05T20:23:58.014Z", + "appCreator": null, + "limits": {}, + "member": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + }, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec58ab9dbd49d2e47c19", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "idMember": "573c6868cbda1f0332684fb0", + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "member": { + "id": "573c6868cbda1f0332684fb0", + "name": "Test User" + } + }, + "type": "addMemberToCard", + "date": "2020-12-05T20:23:52.542Z", + "appCreator": null, + "limits": {}, + "member": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + }, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec48ddecdd4fcdff7dbb", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "idList": "5fcbec13f32ee17fc4780b10" + }, + "card": { + "idList": "5fcbec1161adb425de51ca13", + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "listBefore": { + "id": "5fcbec13f32ee17fc4780b10", + "name": "Done" + }, + "listAfter": { + "id": "5fcbec1161adb425de51ca13", + "name": "In Progress" + } + }, + "type": "updateCard", + "date": "2020-12-05T20:23:36.833Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec466e95cf70fc04fbc7", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "card": { + "id": "5fcbec466e95cf70fc04fbc6", + "name": "Visualize code", + "idShort": 3, + "shortLink": "IutYxjuP" + }, + "list": { + "id": "5fcbec13f32ee17fc4780b10", + "name": "Done" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createCard", + "date": "2020-12-05T20:23:34.998Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec327a174c52fabd612a", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "old": { + "pos": 131071 + }, + "card": { + "pos": 32767.5, + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "list": { + "id": "5fcbec0a23b2cc266296d6a3", + "name": "Todo" + } + }, + "type": "updateCard", + "date": "2020-12-05T20:23:14.540Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec2e6621ca4f0a547897", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "card": { + "id": "5fcbec2e6621ca4f0a547896", + "name": "Write code", + "idShort": 2, + "shortLink": "cQCFhToV" + }, + "list": { + "id": "5fcbec0a23b2cc266296d6a3", + "name": "Todo" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createCard", + "date": "2020-12-05T20:23:10.484Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec28c680d2608374ce8a", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "card": { + "id": "5fcbec28c680d2608374ce89", + "name": "Review code", + "idShort": 1, + "shortLink": "SMc4sR3Q" + }, + "list": { + "id": "5fcbec0a23b2cc266296d6a3", + "name": "Todo" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createCard", + "date": "2020-12-05T20:23:04.103Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec13f32ee17fc4780b12", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "list": { + "id": "5fcbec13f32ee17fc4780b10", + "name": "Done" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createList", + "date": "2020-12-05T20:22:43.566Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec1161adb425de51ca15", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "list": { + "id": "5fcbec1161adb425de51ca13", + "name": "In Progress" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createList", + "date": "2020-12-05T20:22:41.712Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec0a23b2cc266296d6a5", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "list": { + "id": "5fcbec0a23b2cc266296d6a3", + "name": "Todo" + }, + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createList", + "date": "2020-12-05T20:22:34.646Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec03c670ab79e1f625b6", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + }, + "organization": { + "id": "5fcb9620f11c51113570789c", + "name": "Me" + } + }, + "type": "addToOrganizationBoard", + "date": "2020-12-05T20:22:27.087Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + }, + { + "id": "5fcbec03c670ab79e1f625b4", + "idMemberCreator": "573c6868cbda1f0332684fb0", + "data": { + "board": { + "id": "5fcbec03c670ab79e1f625b2", + "name": "Project 100", + "shortLink": "CsjMiavu" + } + }, + "type": "createBoard", + "date": "2020-12-05T20:22:27.083Z", + "appCreator": null, + "limits": {}, + "memberCreator": { + "id": "573c6868cbda1f0332684fb0", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idMemberReferrer": null, + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true + } + } + ], + "cards": [ + { + "id": "5fcbec2e6621ca4f0a547896", + "address": null, + "checkItemStates": null, + "closed": false, + "coordinates": null, + "creationMethod": null, + "dateLastActivity": "2021-01-19T09:18:13.626Z", + "desc": "", + "descData": null, + "dueReminder": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "idLabels": [], + "idList": "5fcbec0a23b2cc266296d6a3", + "idMembersVoted": [], + "idShort": 2, + "idAttachmentCover": null, + "locationName": null, + "manualCoverAttachment": false, + "name": "Write code", + "pos": 32767.5, + "shortLink": "cQCFhToV", + "isTemplate": false, + "cardRole": null, + "badges": { + "attachmentsByType": { + "trello": { + "board": 0, + "card": 0 + } + }, + "location": false, + "votes": 0, + "viewingMemberVoted": false, + "subscribed": true, + "fogbugz": "", + "checkItems": 0, + "checkItemsChecked": 0, + "checkItemsEarliestDue": null, + "comments": 0, + "attachments": 0, + "description": false, + "due": null, + "dueComplete": false, + "start": null + }, + "dueComplete": false, + "due": null, + "email": "test_user+2m8b2b1du8ob4mwcrsg+2vh770etjdr3rxf69hy+1woniecqlh@boards.trello.com", + "idChecklists": [], + "idMembers": ["573c6868cbda1f0332684fb0"], + "labels": [], + "limits": { + "attachments": { + "perCard": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + } + }, + "checklists": { + "perCard": { + "status": "ok", + "disableAt": 500, + "warnAt": 450 + } + }, + "stickers": { + "perCard": { + "status": "ok", + "disableAt": 70, + "warnAt": 63 + } + } + }, + "shortUrl": "https://trello.com/c/cQCFhToV", + "start": null, + "subscribed": true, + "url": "https://trello.com/c/cQCFhToV/2-write-code", + "cover": { + "idAttachment": null, + "color": null, + "idUploadedBackground": null, + "size": "normal", + "brightness": "light" + }, + "attachments": [], + "pluginData": [], + "customFieldItems": [ + { + "id": "6006a3cef37f842d6d0e65d5", + "value": { + "text": "Hello" + }, + "idCustomField": "6006a36ff2036a5d85479f8b", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + }, + { + "id": "6006a3c9d21c1040d78eb551", + "value": { + "date": "2021-01-07T10:00:00.000Z" + }, + "idCustomField": "6006a322ac11ff0bebf5e867", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + }, + { + "id": "6006a3d58801924a07258fe1", + "value": { + "checked": "true" + }, + "idCustomField": "6006a316f6284c2a9d56455f", + "idModel": "5fcbec2e6621ca4f0a547896", + "modelType": "card" + } + ] + }, + { + "id": "5fcbec28c680d2608374ce89", + "address": null, + "checkItemStates": null, + "closed": false, + "coordinates": null, + "creationMethod": null, + "dateLastActivity": "2020-12-05T20:24:09.135Z", + "desc": "", + "descData": null, + "dueReminder": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "idLabels": [], + "idList": "5fcbec0a23b2cc266296d6a3", + "idMembersVoted": [], + "idShort": 1, + "idAttachmentCover": null, + "locationName": null, + "manualCoverAttachment": false, + "name": "Review code", + "pos": 65535, + "shortLink": "SMc4sR3Q", + "isTemplate": false, + "cardRole": null, + "badges": { + "attachmentsByType": { + "trello": { + "board": 0, + "card": 0 + } + }, + "location": false, + "votes": 0, + "viewingMemberVoted": false, + "subscribed": true, + "fogbugz": "", + "checkItems": 0, + "checkItemsChecked": 0, + "checkItemsEarliestDue": null, + "comments": 0, + "attachments": 0, + "description": false, + "due": null, + "dueComplete": false, + "start": null + }, + "dueComplete": false, + "due": null, + "email": "test_user+2m8b2b1du8ob4mwcrsg+2vh76zsxedq1hdyw03t+2213va3qs1@boards.trello.com", + "idChecklists": [], + "idMembers": ["573c6868cbda1f0332684fb0"], + "labels": [], + "limits": { + "attachments": { + "perCard": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + } + }, + "checklists": { + "perCard": { + "status": "ok", + "disableAt": 500, + "warnAt": 450 + } + }, + "stickers": { + "perCard": { + "status": "ok", + "disableAt": 70, + "warnAt": 63 + } + } + }, + "shortUrl": "https://trello.com/c/SMc4sR3Q", + "start": null, + "subscribed": true, + "url": "https://trello.com/c/SMc4sR3Q/1-review-code", + "cover": { + "idAttachment": null, + "color": null, + "idUploadedBackground": null, + "size": "normal", + "brightness": "light" + }, + "attachments": [], + "pluginData": [], + "customFieldItems": [] + }, + { + "id": "5fcbec466e95cf70fc04fbc6", + "address": null, + "checkItemStates": null, + "closed": false, + "coordinates": null, + "creationMethod": null, + "dateLastActivity": "2021-01-19T09:17:18.763Z", + "desc": "", + "descData": null, + "dueReminder": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "idLabels": [], + "idList": "5fcbec1161adb425de51ca13", + "idMembersVoted": [], + "idShort": 3, + "idAttachmentCover": null, + "locationName": null, + "manualCoverAttachment": false, + "name": "Visualize code", + "pos": 65535, + "shortLink": "IutYxjuP", + "isTemplate": false, + "cardRole": null, + "badges": { + "attachmentsByType": { + "trello": { + "board": 0, + "card": 0 + } + }, + "location": false, + "votes": 0, + "viewingMemberVoted": false, + "subscribed": true, + "fogbugz": "", + "checkItems": 0, + "checkItemsChecked": 0, + "checkItemsEarliestDue": null, + "comments": 0, + "attachments": 0, + "description": false, + "due": null, + "dueComplete": false, + "start": null + }, + "dueComplete": false, + "due": null, + "email": "test_user+2m8b2b1du8ob4mwcrsg+2vh7730drc1xx948z46+24yqi73etc@boards.trello.com", + "idChecklists": [], + "idMembers": ["573c6868cbda1f0332684fb0"], + "labels": [], + "limits": { + "attachments": { + "perCard": { + "status": "ok", + "disableAt": 1000, + "warnAt": 900 + } + }, + "checklists": { + "perCard": { + "status": "ok", + "disableAt": 500, + "warnAt": 450 + } + }, + "stickers": { + "perCard": { + "status": "ok", + "disableAt": 70, + "warnAt": 63 + } + } + }, + "shortUrl": "https://trello.com/c/IutYxjuP", + "start": null, + "subscribed": true, + "url": "https://trello.com/c/IutYxjuP/3-visualize-code", + "cover": { + "idAttachment": null, + "color": null, + "idUploadedBackground": null, + "size": "normal", + "brightness": "light" + }, + "attachments": [], + "pluginData": [], + "customFieldItems": [ + { + "id": "6006a39e35b0703fd198d92e", + "value": { + "text": "Test text" + }, + "idCustomField": "6006a36ff2036a5d85479f8b", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + { + "id": "6006a39120b4fb4013812fed", + "value": { + "number": "100.32" + }, + "idCustomField": "6006a365e69d965129190ad5", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + { + "id": "6006a38aa44102505b0df553", + "idValue": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + { + "id": "6006a387be5e6f36b610d694", + "value": { + "date": "2021-01-28T10:00:00.000Z" + }, + "idCustomField": "6006a322ac11ff0bebf5e867", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + }, + { + "id": "6006a37bfaecc82b4f05b64f", + "value": { + "checked": "true" + }, + "idCustomField": "6006a316f6284c2a9d56455f", + "idModel": "5fcbec466e95cf70fc04fbc6", + "modelType": "card" + } + ] + } + ], + "labels": [ + { + "id": "5fcbec031258da48af166d5f", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "green" + }, + { + "id": "5fcbec031258da48af166d61", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "yellow" + }, + { + "id": "5fcbec031258da48af166d67", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "red" + }, + { + "id": "5fcbec031258da48af166d69", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "purple" + }, + { + "id": "5fcbec031258da48af166d65", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "orange" + }, + { + "id": "5fcbec031258da48af166d6a", + "idBoard": "5fcbec03c670ab79e1f625b2", + "name": "", + "color": "blue" + } + ], + "lists": [ + { + "id": "5fcbec0a23b2cc266296d6a3", + "name": "Todo", + "closed": false, + "pos": 65535, + "softLimit": null, + "creationMethod": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "limits": { + "cards": { + "openPerList": { + "status": "ok", + "disableAt": 5000, + "warnAt": 4500 + }, + "totalPerList": { + "status": "ok", + "disableAt": 1000000, + "warnAt": 900000 + } + } + }, + "subscribed": false + }, + { + "id": "5fcbec1161adb425de51ca13", + "name": "In Progress", + "closed": false, + "pos": 131071, + "softLimit": null, + "creationMethod": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "limits": { + "cards": { + "openPerList": { + "status": "ok", + "disableAt": 5000, + "warnAt": 4500 + }, + "totalPerList": { + "status": "ok", + "disableAt": 1000000, + "warnAt": 900000 + } + } + }, + "subscribed": false + }, + { + "id": "5fcbec13f32ee17fc4780b10", + "name": "Done", + "closed": false, + "pos": 196607, + "softLimit": null, + "creationMethod": null, + "idBoard": "5fcbec03c670ab79e1f625b2", + "limits": { + "cards": { + "openPerList": { + "status": "ok", + "disableAt": 5000, + "warnAt": 4500 + }, + "totalPerList": { + "status": "ok", + "disableAt": 1000000, + "warnAt": 900000 + } + } + }, + "subscribed": false + } + ], + "members": [ + { + "id": "573c6868cbda1f0332684fb0", + "bio": "", + "bioData": { + "emoji": {} + }, + "confirmed": true, + "memberType": "normal", + "username": "test_user", + "activityBlocked": false, + "fullName": "Test User", + "idEnterprise": null, + "idEnterprisesDeactivated": [], + "idMemberReferrer": null, + "idPremOrgsAdmin": [], + "initials": "J", + "nonPublic": { + "fullName": "Test User", + "initials": "TU" + }, + "nonPublicAvailable": true, + "products": [], + "url": "https://trello.com/test_user", + "status": "disconnected" + } + ], + "checklists": [], + "customFields": [ + { + "id": "6006a316f6284c2a9d56455f", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "a4f4de7bc77246dd12034d1f16c6d59bbebcb99f5619b4cf81d6f49df0f3c682", + "display": { + "cardFront": true + }, + "name": "Logical Value", + "pos": 16384, + "type": "checkbox", + "isSuggestedField": false + }, + { + "id": "6006a322ac11ff0bebf5e867", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "20f90e512f069e385e9418c9ded0d2bd72bf0ecf6709fc4efec517c563308164", + "display": { + "cardFront": true + }, + "name": "Date Value", + "pos": 32768, + "type": "date", + "isSuggestedField": false + }, + { + "id": "6006a33439b34a6d32962e40", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "dc10651a093834befed19308dd27fbdb6f552375b4e20e9b1e49fbd5dce97c2d", + "display": { + "cardFront": true + }, + "name": "Dropdown", + "pos": 49152, + "options": [ + { + "id": "6006a33d53a30716478adbff", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Up" + }, + "color": "none", + "pos": 16384 + }, + { + "id": "6006a33fecf9373dd454b422", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Down" + }, + "color": "none", + "pos": 32768 + }, + { + "id": "6006a341d1195d325a5401d7", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "Low" + }, + "color": "none", + "pos": 49152 + }, + { + "id": "6006a342211c877049a0d095", + "idCustomField": "6006a33439b34a6d32962e40", + "value": { + "text": "High" + }, + "color": "none", + "pos": 65536 + } + ], + "type": "list", + "isSuggestedField": false + }, + { + "id": "6006a365e69d965129190ad5", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "756d4f404f7558e03b37f4edd136665e83fa8d1e1630f44c569fe2f8dcd111f6", + "display": { + "cardFront": true + }, + "name": "Number Value", + "pos": 65536, + "type": "number", + "isSuggestedField": false + }, + { + "id": "6006a36ff2036a5d85479f8b", + "idModel": "5fcbec03c670ab79e1f625b2", + "modelType": "board", + "fieldGroup": "4b058f632fa5bdedb12f506c22205dd145baf411bc6385e86771cd3637818dd8", + "display": { + "cardFront": true + }, + "name": "Text Value", + "pos": 81920, + "type": "text", + "isSuggestedField": false + } + ], + "memberships": [ + { + "id": "5fcbec03c670ab79e1f625b3", + "idMember": "573c6868cbda1f0332684fb0", + "memberType": "admin", + "unconfirmed": false, + "deactivated": false + } + ], + "pluginData": [] +} diff --git a/wekan-dockerfile-manifest.yaml b/wekan-dockerfile-manifest.yaml new file mode 100644 index 000000000..9242f7d7e --- /dev/null +++ b/wekan-dockerfile-manifest.yaml @@ -0,0 +1,11 @@ +image: quay.io/wekan/wekan +manifests: + - image: quay.io/wekan/wekan:amd64 + platform: + architecture: amd64 + os: linux + - image: quay.io/wekan/wekan:arm64v8 + platform: + architecture: arm64 + os: linux + variant: v8